Nikolay Suslov 7 years ago
parent
commit
91f9faa9ec

+ 1 - 2
README.md

@@ -32,5 +32,4 @@ npm run
 <img src="./public/doc/images/avatar.jpg" width="400">
 <br>
 
-## Docs
-..soon
+## Docs

+ 1 - 1
public/index.html

@@ -64,7 +64,7 @@
 
   <script>
     //var socket = initWebSocket();
-    window.onload = getAppDetails();
+    window.onload = getAppDetails('webapps.json');
   </script>
 
   <script>

+ 266 - 265
public/web/lib/app.js

@@ -1,292 +1,293 @@
 var options = {
-
-    query: 'pathname=' + window.location.pathname.slice(1,
-        window.location.pathname.lastIndexOf("/")),
-    secure: window.location.protocol === "https:",
-    reconnection: false,
-    transports: ['websocket']
-
-};
-
-var socket = io.connect(window.location.protocol + "//" + window.location.host, options);
-
-socket.on('getWebAppUpdate', function (msg) {
-    parseAppInstancesData(msg)
-    //console.log(msg);
-});
-
-
-function parseAppInstancesData(data) {
-
-    var needToUpdate = true;
-
-    if (data == "{}") {
-        var el = document.querySelector(".instance");
-        if (el) {
-            var topEl = el.parentNode;
-            topEl.removeChild(el);
+    
+        query: 'pathname=' + window.location.pathname.slice(1,
+            window.location.pathname.lastIndexOf("/")),
+        secure: window.location.protocol === "https:",
+        reconnection: false,
+        transports: ['websocket']
+    
+    };
+    
+    var socket = io.connect(window.location.protocol + "//" + window.location.host, options);
+    
+    socket.on('getWebAppUpdate', function (msg) {
+        parseAppInstancesData(msg)
+        //console.log(msg);
+    });
+    
+    
+    function parseAppInstancesData(data) {
+    
+        var needToUpdate = true;
+    
+        if (data == "{}") {
+            var el = document.querySelector(".instance");
+            if (el) {
+                var topEl = el.parentNode;
+                topEl.removeChild(el);
+            }
+            // let removeElements = elms => Array.from(elms).forEach(el => el.remove()); 
         }
-        // let removeElements = elms => Array.from(elms).forEach(el => el.remove()); 
-    }
-
-    let jsonObj = JSON.parse(data);
-    var parsed = {};
-    for (var prop in jsonObj) {
-        var name = prop.split('/')[1];
-        if (parsed[name]) {
-            parsed[name][prop] = jsonObj[prop];
-        } else {
-            parsed[name] = {};
-            parsed[name][prop] = jsonObj[prop];
+    
+        let jsonObj = JSON.parse(data);
+        var parsed = {};
+        for (var prop in jsonObj) {
+            var name = prop.split('/')[1];
+            if (parsed[name]) {
+                parsed[name][prop] = jsonObj[prop];
+            } else {
+                parsed[name] = {};
+                parsed[name][prop] = jsonObj[prop];
+            }
+    
         }
-
-    }
-    //console.log(parsed);
-
-    document.querySelector("#main")._emptyLists();
-
-    for (var prop in parsed) {
-        var name = prop;
-        let element = document.getElementById(name + 'List');
-        if (element) {
-            element._setListData(parsed[prop]);
+        //console.log(parsed);
+    
+        document.querySelector("#main")._emptyLists();
+    
+        for (var prop in parsed) {
+            var name = prop;
+            let element = document.getElementById(name + 'List');
+            if (element) {
+                element._setListData(parsed[prop]);
+            }
+            //needToUpdate = true
         }
-        //needToUpdate = true
+        // console.log(data)
     }
-    // console.log(data)
-}
-
-function getAllAppInstances() {
-
-    let allInatances = httpGetJson('allinstances.json')
-        .then(res => {
-            parseAppInstancesData(res);
-        });
-}
-
-function parseWebAppDataForCell(data) {
-
-    document.querySelector("#main").$cell({
-        $cell: true,
-        $type: "div",
-        id: "main",
-        _jsonData: {},
-        _emptyLists: function () {
-            Object.entries(this._jsonData).forEach(function (element) {
-                //console.log(element);
-                document.getElementById(element[0] + 'List')._setListData({});
+    
+    function getAllAppInstances() {
+    
+        let allInatances = httpGetJson('allinstances.json')
+            .then(res => {
+                parseAppInstancesData(res);
             });
-        },
-        $init: function () {
-            this._jsonData = JSON.parse(data);
-        },
-        _makeWorldCard: function (m) {
-            return {
-                $cell: true,
-                $type: "div",
-                class: "mdc-layout-grid__cell mdc-layout-grid__cell--span-4",
-                $components: [
-                    this._worldCardDef(m)
-                ]
-            }
-
-        },
-        $update: function () {
-            this.$components = [
-                // siteHeader,
-                {
+    }
+    
+    function parseWebAppDataForCell(data) {
+    
+        document.querySelector("#main").$cell({
+            $cell: true,
+            $type: "div",
+            id: "main",
+            _jsonData: {},
+            _emptyLists: function () {
+                Object.entries(this._jsonData).forEach(function (element) {
+                    //console.log(element);
+                    document.getElementById(element[0] + 'List')._setListData({});
+                });
+            },
+            $init: function () {
+                this._jsonData = JSON.parse(data);
+            },
+            _makeWorldCard: function (m) {
+                return {
+                    $cell: true,
                     $type: "div",
-                    class: "mdc-layout-grid",
+                    class: "mdc-layout-grid__cell mdc-layout-grid__cell--span-4",
                     $components: [
-                        {
-                            $type: "div",
-                            class: "mdc-layout-grid__inner",
-                            $components: Object.entries(this._jsonData).map(this._makeWorldCard)
-                        }
+                        this._worldCardDef(m)
                     ]
-
-                },
-
-
-            ]
-        },
-        _worldCardDef: function (desc) {
-
-            return {
-                $cell: true,
-                $type: "div",
-                class: "mdc-card world-card",
-                $components: [
+                }
+    
+            },
+            $update: function () {
+                this.$components = [
+                    // siteHeader,
                     {
-                        $type: "section",
-                        class: "mdc-card__media world-card__16-9-media",
-                        $init: function () {
-                            if (desc[1].imgUrl !== "") {
-                                this.style.backgroundImage = 'linear-gradient(0deg, rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0.5) ), url(' + desc[1].imgUrl + ')';
-                            }
-                        }
-                    },
-                    {
-                        $type: "section",
-                        class: "mdc-card__primary",
+                        $type: "div",
+                        class: "mdc-layout-grid",
                         $components: [
                             {
-                                $type: "h1",
-                                class: "mdc-card__title mdc-card__title--large",
-                                $text: desc[1].title
-                            },
-                            {
-                                $type: "h2",
-                                class: "mdc-card__subtitle mdc-theme--text-secondary-on-background",
-                                $text: desc[1].text
+                                $type: "div",
+                                class: "mdc-layout-grid__inner",
+                                $components: Object.entries(this._jsonData).map(this._makeWorldCard)
                             }
                         ]
+    
                     },
-                    {
-                        $type: "section",
-                        class: "mdc-card__actions",
-                        $components: [
-                            {
-                                $type: "a",
-                                class: "mdc-button mdc-button--compact mdc-card__action mdc-button--raised",
-                                $text: "Start new",
-                                target: "_blank",
-                                href: "/" + desc[0],
-                                onclick: function (e) {
-                                    refresh();
+    
+    
+                ]
+            },
+            _worldCardDef: function (desc) {
+    
+                return {
+                    $cell: true,
+                    $type: "div",
+                    class: "mdc-card world-card",
+                    $components: [
+                        {
+                            $type: "section",
+                            class: "mdc-card__media world-card__16-9-media",
+                            $init: function () {
+                                if (desc[1].imgUrl !== "") {
+                                    this.style.backgroundImage = 'linear-gradient(0deg, rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0.5) ), url(' + desc[1].imgUrl + ')';
                                 }
                             }
-
-
-                        ]
-                    },
-
-                    {
-                        $type: "section",
-                        class: "mdc-card__actions",
-                        $components: [
-
-                            {
-                                $type: "ul",
-                                _listData: {},
-                                _setListData: function (data) {
-                                    this._listData = data;
+                        },
+                        {
+                            $type: "section",
+                            class: "mdc-card__primary",
+                            $components: [
+                                {
+                                    $type: "h1",
+                                    class: "mdc-card__title mdc-card__title--large",
+                                    $text: desc[1].title
                                 },
-                                class: "mdc-list mdc-list--two-line",
-                                id: desc[0] + 'List',
-                                $update: function () {
-                                    var connectText = {}
-
-                                    if (Object.entries(this._listData).length !== 0) {
-                                        connectText = {
-                                            // $type: "span",
-                                            // class: "mdc-theme--text-secondary",
-                                            // $text: "...or connect to:"
-                                        }
+                                {
+                                    $type: "h2",
+                                    class: "mdc-card__subtitle mdc-theme--text-secondary-on-background",
+                                    $text: desc[1].text
+                                }
+                            ]
+                        },
+                        {
+                            $type: "section",
+                            class: "mdc-card__actions",
+                            $components: [
+                                {
+                                    $type: "a",
+                                    class: "mdc-button mdc-button--compact mdc-card__action mdc-button--raised",
+                                    $text: "Start new",
+                                    target: "_blank",
+                                    href: "/" + desc[0],
+                                    onclick: function (e) {
+                                        refresh();
                                     }
-                                    this.$components = [
-                                        {
-                                            $type: "hr",
-                                            class: "mdc-list-divider"
+                                }
+    
+    
+                            ]
+                        },
+    
+                        {
+                            $type: "section",
+                            class: "mdc-card__actions",
+                            $components: [
+    
+                                {
+                                    $type: "ul",
+                                    _listData: {},
+                                    _setListData: function (data) {
+                                        this._listData = data;
+                                    },
+                                    class: "mdc-list mdc-list--two-line",
+                                    id: desc[0] + 'List',
+                                    $update: function () {
+                                        var connectText = {}
+    
+                                        if (Object.entries(this._listData).length !== 0) {
+                                            connectText = {
+                                                // $type: "span",
+                                                // class: "mdc-theme--text-secondary",
+                                                // $text: "...or connect to:"
+                                            }
                                         }
-                                    ].concat(Object.entries(this._listData).map(this._worldListItem))
-                                    //     [connectText]
-                                    // }].concat(Object.entries(this._listData).map(this._worldListItem))
-
-
-                                },
-                                _worldListItem: function (m) {
-                                    return {
-                                        $type: "li",
-                                        class: "mdc-list-item",
-                                        $components: [
+                                        this.$components = [
                                             {
-                                                $type: "span",
-                                                class: "world-link mdc-list-item__text",
-                                                $components: [
-                                                    {
-                                                        $type: "a",
-                                                        $text: m[1].instance,
-                                                        target: "_blank",
-                                                        href: "http://" + m[1].instance,
-                                                        onclick: function (e) {
-                                                            refresh();
-                                                        }
-                                                    },
-                                                    {
-                                                        $type: "span",
-                                                        class: "mdc-list-item__text__secondary",
-                                                        $text: "Users online: " + m[1].clients
-                                                    }
-                                                ]
+                                                $type: "hr",
+                                                class: "mdc-list-divider"
                                             }
-
-
-
-                                        ]
-
+                                        ].concat(Object.entries(this._listData).map(this._worldListItem))
+                                        //     [connectText]
+                                        // }].concat(Object.entries(this._listData).map(this._worldListItem))
+    
+    
+                                    },
+                                    _worldListItem: function (m) {
+                                        return {
+                                            $type: "li",
+                                            class: "mdc-list-item",
+                                            $components: [
+                                                {
+                                                    $type: "span",
+                                                    class: "world-link mdc-list-item__text",
+                                                    $components: [
+                                                        {
+                                                            $type: "a",
+                                                            $text: m[1].instance,
+                                                            target: "_blank",
+                                                            href: window.location.protocol + "//" + m[1].instance,
+                                                            onclick: function (e) {
+                                                                refresh();
+                                                            }
+                                                        },
+                                                        {
+                                                            $type: "span",
+                                                            class: "mdc-list-item__text__secondary",
+                                                            $text: "Users online: " + m[1].clients
+                                                        }
+                                                    ]
+                                                }
+    
+    
+    
+                                            ]
+    
+                                        }
                                     }
                                 }
-                            }
-                        ]
-                    }
-                ]
+                            ]
+                        }
+                    ]
+                }
             }
-        }
-
-
-    })
-
-
-
-}
-
-function getAppDetails() {
-
-    let appDetails = httpGetJson('webapps.json')
-        .then(res => {
-            parseWebAppDataForCell(res)
+    
+    
         })
-        .then(res => refresh());
-}
-
-
-function refresh() {
-    // socket.emit('getWebAppUpdate', "");
-}
-
-
-function httpGet(url) {
-    return new Promise(function (resolve, reject) {
-        // do the usual Http request
-        let request = new XMLHttpRequest();
-        request.open('GET', url);
-
-        request.onload = function () {
-            if (request.status == 200) {
-                resolve(request.response);
-            } else {
-                reject(Error(request.statusText));
-            }
-        };
-
-        request.onerror = function () {
-            reject(Error('Network Error'));
-        };
-
-        request.send();
-    });
-}
-async function httpGetJson(url) {
-    // check if the URL looks like a JSON file and call httpGet.
-    let regex = /\.(json)$/i;
-
-    if (regex.test(url)) {
-        // call the async function, wait for the result
-        return await httpGet(url);
-    } else {
-        throw Error('Bad Url Format');
+    
+    
+    
+    }
+    
+    function getAppDetails(val) {
+    
+        let appDetails = httpGetJson(val)
+            .then(res => {
+                parseWebAppDataForCell(res)
+            })
+            .then(res => refresh());
+    }
+    
+    
+    function refresh() {
+        // socket.emit('getWebAppUpdate', "");
+    }
+    
+    
+    function httpGet(url) {
+        return new Promise(function (resolve, reject) {
+            // do the usual Http request
+            let request = new XMLHttpRequest();
+            request.open('GET', url);
+    
+            request.onload = function () {
+                if (request.status == 200) {
+                    resolve(request.response);
+                } else {
+                    reject(Error(request.statusText));
+                }
+            };
+    
+            request.onerror = function () {
+                reject(Error('Network Error'));
+            };
+    
+            request.send();
+        });
+    }
+    async function httpGetJson(url) {
+        // check if the URL looks like a JSON file and call httpGet.
+        let regex = /\.(json)$/i;
+    
+        if (regex.test(url)) {
+            // call the async function, wait for the result
+            return await httpGet(url);
+        } else {
+            throw Error('Bad Url Format');
+        }
     }
-}
-
-
+    
+    
+    

+ 60 - 3
support/client/lib/vwf/view/editor-new.js

@@ -44,6 +44,7 @@ define([
             this.allScripts = {};
 
 
+
             //$(document.head).append('<style type="text/css" media="screen"> #editorlive { height: 500px; width: 800px; } </style>');
             document.querySelector('head').innerHTML += '<style type="text/css" media="screen"> #editorlive { height: 500px; width: 800px; } </style>';
             document.querySelector('head').innerHTML += '<link rel="stylesheet" href="vwf/view/lib/editorLive.css">';
@@ -122,14 +123,18 @@ define([
                 return root
             };
 
-            
+            this.getRandomInt = function (min, max) {
+                min = Math.ceil(min);
+                max = Math.floor(max);
+                return Math.floor(Math.random() * (max - min)) + min; //The maximum is exclusive and the minimum is inclusive
+            };
 
             ["drawer", "toolbar", "sideBar", "propWindow", "clientsWindow", "codeEditorWindow", "propEditorWindow", "viewSceneProps"].forEach(item => {
                 let el = document.createElement("div");
                 el.setAttribute("id", item);
                 document.body.appendChild(el);
             }
-                );
+            );
 
             this.avatarCardDef = function (src, desc, onclickfunc) {
 
@@ -192,6 +197,58 @@ define([
                                     class: "mdc-layout-grid__cell mdc-layout-grid__cell--span-12",
                                     $components: [
                                         {
+                                            $cell: true,
+                                            $type: "button",
+                                            class: "mdc-button mdc-button--raised",
+                                            $text: "Go forward",
+                                            onclick: function (e) {
+
+                                                function getMovementVector(el) {
+                                                    var directionVector = new THREE.Vector3(0, 0, 0);
+                                                    var rotationEuler = new THREE.Euler(0, 0, 0, 'YXZ');
+
+                                                    var rotation = el.getAttribute('rotation');
+                                                    var velocity = new THREE.Vector3(0, 0, -0.5);
+                                                    var xRotation;
+
+                                                    directionVector.copy(velocity);
+                                                    directionVector.multiplyScalar(1.0);
+
+                                                    // Absolute.
+                                                    if (!rotation) { return directionVector; }
+
+                                                    xRotation = 0;
+
+                                                    // Transform direction relative to heading.
+                                                    rotationEuler.set(THREE.Math.degToRad(xRotation), THREE.Math.degToRad(rotation.y), 0);
+                                                    directionVector.applyEuler(rotationEuler);
+                                                    return directionVector;
+
+                                                }
+
+                                                let el = document.querySelector('#avatarControl');
+
+                                                let currentPosition = el.getAttribute('position');
+                                                let movementVector = getMovementVector(el);
+                                                let position = {};
+
+                                                position.x = currentPosition.x + movementVector.x;
+                                                position.y = currentPosition.y + movementVector.y;
+                                                position.z = currentPosition.z + movementVector.z;
+                                                el.setAttribute('position', position);
+
+                                            }
+
+                                        },
+                                    ]
+                                },
+                                {
+                                    $cell: true,
+                                    $type: "div",
+                                    class: "mdc-layout-grid__cell mdc-layout-grid__cell--span-12",
+                                    $components: [
+                                        {
+                                        
                                             $cell: true,
                                             $type: "button",
                                             class: "mdc-button mdc-button--raised",
@@ -2855,7 +2912,7 @@ define([
         return childNode;
     };
 
-    
+
 
     // -- viewScript ------------------------------------------------------------------------
 

+ 1412 - 0
support/client/lib/vwf/view/lib/nipplejs/nipplejs.js

@@ -0,0 +1,1412 @@
+(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.nipplejs = f()}})(function(){var define,module,exports;
+'use strict';
+
+// Constants
+var isTouch = !!('ontouchstart' in window);
+var isPointer = window.PointerEvent ? true : false;
+var isMSPointer = window.MSPointerEvent ? true : false;
+var events = {
+    touch: {
+        start: 'touchstart',
+        move: 'touchmove',
+        end: 'touchend'
+    },
+    mouse: {
+        start: 'mousedown',
+        move: 'mousemove',
+        end: 'mouseup'
+    },
+    pointer: {
+        start: 'pointerdown',
+        move: 'pointermove',
+        end: 'pointerup'
+    },
+    MSPointer: {
+        start: 'MSPointerDown',
+        move: 'MSPointerMove',
+        end: 'MSPointerUp'
+    }
+};
+var toBind;
+var secondBind = {};
+if (isPointer) {
+    toBind = events.pointer;
+} else if (isMSPointer) {
+    toBind = events.MSPointer;
+} else if (isTouch) {
+    toBind = events.touch;
+    secondBind = events.mouse;
+} else {
+    toBind = events.mouse;
+}
+///////////////////////
+///      UTILS      ///
+///////////////////////
+
+var u = {};
+u.distance = function (p1, p2) {
+    var dx = p2.x - p1.x;
+    var dy = p2.y - p1.y;
+
+    return Math.sqrt((dx * dx) + (dy * dy));
+};
+
+u.angle = function(p1, p2) {
+    var dx = p2.x - p1.x;
+    var dy = p2.y - p1.y;
+
+    return u.degrees(Math.atan2(dy, dx));
+};
+
+u.findCoord = function(p, d, a) {
+    var b = {x: 0, y: 0};
+    a = u.radians(a);
+    b.x = p.x - d * Math.cos(a);
+    b.y = p.y - d * Math.sin(a);
+    return b;
+};
+
+u.radians = function(a) {
+    return a * (Math.PI / 180);
+};
+
+u.degrees = function(a) {
+    return a * (180 / Math.PI);
+};
+
+u.bindEvt = function (el, type, handler) {
+    if (el.addEventListener) {
+        el.addEventListener(type, handler, false);
+    } else if (el.attachEvent) {
+        el.attachEvent(type, handler);
+    }
+};
+
+u.unbindEvt = function (el, type, handler) {
+    if (el.removeEventListener) {
+        el.removeEventListener(type, handler);
+    } else if (el.detachEvent) {
+        el.detachEvent(type, handler);
+    }
+};
+
+u.trigger = function (el, type, data) {
+    var evt = new CustomEvent(type, data);
+    el.dispatchEvent(evt);
+};
+
+u.prepareEvent = function (evt) {
+    evt.preventDefault();
+    return evt.type.match(/^touch/) ? evt.changedTouches : evt;
+};
+
+u.getScroll = function () {
+    var x = (window.pageXOffset !== undefined) ?
+        window.pageXOffset :
+        (document.documentElement || document.body.parentNode || document.body)
+            .scrollLeft;
+
+    var y = (window.pageYOffset !== undefined) ?
+        window.pageYOffset :
+        (document.documentElement || document.body.parentNode || document.body)
+            .scrollTop;
+    return {
+        x: x,
+        y: y
+    };
+};
+
+u.applyPosition = function (el, pos) {
+    if (pos.x && pos.y) {
+        el.style.left = pos.x + 'px';
+        el.style.top = pos.y + 'px';
+    } else if (pos.top || pos.right || pos.bottom || pos.left) {
+        el.style.top = pos.top;
+        el.style.right = pos.right;
+        el.style.bottom = pos.bottom;
+        el.style.left = pos.left;
+    }
+};
+
+u.getTransitionStyle = function (property, values, time) {
+    var obj = u.configStylePropertyObject(property);
+    for (var i in obj) {
+        if (obj.hasOwnProperty(i)) {
+            if (typeof values === 'string') {
+                obj[i] = values + ' ' + time;
+            } else {
+                var st = '';
+                for (var j = 0, max = values.length; j < max; j += 1) {
+                    st += values[j] + ' ' + time + ', ';
+                }
+                obj[i] = st.slice(0, -2);
+            }
+        }
+    }
+    return obj;
+};
+
+u.getVendorStyle = function (property, value) {
+    var obj = u.configStylePropertyObject(property);
+    for (var i in obj) {
+        if (obj.hasOwnProperty(i)) {
+            obj[i] = value;
+        }
+    }
+    return obj;
+};
+
+u.configStylePropertyObject = function (prop) {
+    var obj = {};
+    obj[prop] = '';
+    var vendors = ['webkit', 'Moz', 'o'];
+    vendors.forEach(function (vendor) {
+        obj[vendor + prop.charAt(0).toUpperCase() + prop.slice(1)] = '';
+    });
+    return obj;
+};
+
+u.extend = function (objA, objB) {
+    for (var i in objB) {
+        if (objB.hasOwnProperty(i)) {
+            objA[i] = objB[i];
+        }
+    }
+    return objA;
+};
+
+// Overwrite only what's already present
+u.safeExtend = function (objA, objB) {
+    var obj = {};
+    for (var i in objA) {
+        if (objA.hasOwnProperty(i) && objB.hasOwnProperty(i)) {
+            obj[i] = objB[i];
+        } else if (objA.hasOwnProperty(i)) {
+            obj[i] = objA[i];
+        }
+    }
+    return obj;
+};
+
+// Map for array or unique item.
+u.map = function (ar, fn) {
+    if (ar.length) {
+        for (var i = 0, max = ar.length; i < max; i += 1) {
+            fn(ar[i]);
+        }
+    } else {
+        fn(ar);
+    }
+};
+///////////////////////
+///   SUPER CLASS   ///
+///////////////////////
+
+function Super () {};
+
+// Basic event system.
+Super.prototype.on = function (arg, cb) {
+    var self = this;
+    var types = arg.split(/[ ,]+/g);
+    var type;
+    self._handlers_ = self._handlers_ || {};
+
+    for (var i = 0; i < types.length; i += 1) {
+        type = types[i];
+        self._handlers_[type] = self._handlers_[type] || [];
+        self._handlers_[type].push(cb);
+    }
+    return self;
+};
+
+Super.prototype.off = function (type, cb) {
+    var self = this;
+    self._handlers_ = self._handlers_ || {};
+
+    if (type === undefined) {
+        self._handlers_ = {};
+    } else if (cb === undefined) {
+        self._handlers_[type] = null;
+    } else if (self._handlers_[type] &&
+            self._handlers_[type].indexOf(cb) >= 0) {
+        self._handlers_[type].splice(self._handlers_[type].indexOf(cb), 1);
+    }
+
+    return self;
+};
+
+Super.prototype.trigger = function (arg, data) {
+    var self = this;
+    var types = arg.split(/[ ,]+/g);
+    var type;
+    self._handlers_ = self._handlers_ || {};
+
+    for (var i = 0; i < types.length; i += 1) {
+        type = types[i];
+        if (self._handlers_[type] && self._handlers_[type].length) {
+            self._handlers_[type].forEach(function (handler) {
+                handler.call(self, {
+                    type: type,
+                    target: self
+                }, data);
+            });
+        }
+    }
+};
+
+// Configuration
+Super.prototype.config = function (options) {
+    var self = this;
+    self.options = self.defaults || {};
+    if (options) {
+        self.options = u.safeExtend(self.options, options);
+    }
+};
+
+// Bind internal events.
+Super.prototype.bindEvt = function (el, type) {
+    var self = this;
+    self._domHandlers_ = self._domHandlers_ || {};
+
+    self._domHandlers_[type] = function () {
+        if (typeof self['on' + type] === 'function') {
+            self['on' + type].apply(self, arguments);
+        } else {
+            console.warn('[WARNING] : Missing "on' + type + '" handler.');
+        }
+    };
+
+    u.bindEvt(el, toBind[type], self._domHandlers_[type]);
+
+    if (secondBind[type]) {
+        // Support for both touch and mouse at the same time.
+        u.bindEvt(el, secondBind[type], self._domHandlers_[type]);
+    }
+
+    return self;
+};
+
+// Unbind dom events.
+Super.prototype.unbindEvt = function (el, type) {
+    var self = this;
+    self._domHandlers_ = self._domHandlers_ || {};
+
+    u.unbindEvt(el, toBind[type], self._domHandlers_[type]);
+
+    if (secondBind[type]) {
+        // Support for both touch and mouse at the same time.
+        u.unbindEvt(el, secondBind[type], self._domHandlers_[type]);
+    }
+
+    delete self._domHandlers_[type];
+
+    return this;
+};
+///////////////////////
+///   THE NIPPLE    ///
+///////////////////////
+
+function Nipple (collection, options) {
+    this.identifier = options.identifier;
+    this.position = options.position;
+    this.frontPosition = options.frontPosition;
+    this.collection = collection;
+
+    // Defaults
+    this.defaults = {
+        size: 100,
+        threshold: 0.1,
+        color: 'white',
+        fadeTime: 250,
+        dataOnly: false,
+        restOpacity: 0.5,
+        mode: 'dynamic',
+        zone: document.body
+    };
+
+    this.config(options);
+
+    // Overwrites
+    if (this.options.mode === 'dynamic') {
+        this.options.restOpacity = 0;
+    }
+
+    this.id = Nipple.id;
+    Nipple.id += 1;
+    this.buildEl()
+        .stylize();
+
+    // Nipple's API.
+    this.instance = {
+        el: this.ui.el,
+        on: this.on.bind(this),
+        off: this.off.bind(this),
+        show: this.show.bind(this),
+        hide: this.hide.bind(this),
+        add: this.addToDom.bind(this),
+        remove: this.removeFromDom.bind(this),
+        destroy: this.destroy.bind(this),
+        resetDirection: this.resetDirection.bind(this),
+        computeDirection: this.computeDirection.bind(this),
+        trigger: this.trigger.bind(this),
+        position: this.position,
+        frontPosition: this.frontPosition,
+        ui: this.ui,
+        identifier: this.identifier,
+        id: this.id,
+        options: this.options
+    };
+
+    return this.instance;
+};
+
+Nipple.prototype = new Super();
+Nipple.constructor = Nipple;
+Nipple.id = 0;
+
+// Build the dom element of the Nipple instance.
+Nipple.prototype.buildEl = function (options) {
+    this.ui = {};
+
+    if (this.options.dataOnly) {
+        return this;
+    }
+
+    this.ui.el = document.createElement('div');
+    this.ui.back = document.createElement('div');
+    this.ui.front = document.createElement('div');
+
+    this.ui.el.className = 'nipple collection_' + this.collection.id;
+    this.ui.back.className = 'back';
+    this.ui.front.className = 'front';
+
+    this.ui.el.setAttribute('id', 'nipple_' + this.collection.id +
+        '_' + this.id);
+
+    this.ui.el.appendChild(this.ui.back);
+    this.ui.el.appendChild(this.ui.front);
+
+    return this;
+};
+
+// Apply CSS to the Nipple instance.
+Nipple.prototype.stylize = function () {
+    if (this.options.dataOnly) {
+        return this;
+    }
+    var animTime = this.options.fadeTime + 'ms';
+    var borderStyle = u.getVendorStyle('borderRadius', '50%');
+    var transitStyle = u.getTransitionStyle('transition', 'opacity', animTime);
+    var styles = {};
+    styles.el = {
+        position: 'absolute',
+        opacity: this.options.restOpacity,
+        display: 'block',
+        'zIndex': 999
+    };
+
+    styles.back = {
+        position: 'absolute',
+        display: 'block',
+        width: this.options.size + 'px',
+        height: this.options.size + 'px',
+        marginLeft: -this.options.size / 2 + 'px',
+        marginTop: -this.options.size / 2 + 'px',
+        background: this.options.color,
+        'opacity': '.5'
+    };
+
+    styles.front = {
+        width: this.options.size / 2 + 'px',
+        height: this.options.size / 2 + 'px',
+        position: 'absolute',
+        display: 'block',
+        marginLeft: -this.options.size / 4 + 'px',
+        marginTop: -this.options.size / 4 + 'px',
+        background: this.options.color,
+        'opacity': '.5'
+    };
+
+    u.extend(styles.el, transitStyle);
+    u.extend(styles.back, borderStyle);
+    u.extend(styles.front, borderStyle);
+
+    this.applyStyles(styles);
+
+    return this;
+};
+
+Nipple.prototype.applyStyles = function (styles) {
+    // Apply styles
+    for (var i in this.ui) {
+        if (this.ui.hasOwnProperty(i)) {
+            for (var j in styles[i]) {
+                this.ui[i].style[j] = styles[i][j];
+            }
+        }
+    }
+
+    return this;
+};
+
+// Inject the Nipple instance into DOM.
+Nipple.prototype.addToDom = function () {
+    // We're not adding it if we're dataOnly or already in dom.
+    if (this.options.dataOnly || document.body.contains(this.ui.el)) {
+        return this;
+    }
+    this.options.zone.appendChild(this.ui.el);
+    return this;
+};
+
+// Remove the Nipple instance from DOM.
+Nipple.prototype.removeFromDom = function () {
+    if (this.options.dataOnly || !document.body.contains(this.ui.el)) {
+        return this;
+    }
+    this.options.zone.removeChild(this.ui.el);
+    return this;
+};
+
+// Entirely destroy this nipple
+Nipple.prototype.destroy = function () {
+    clearTimeout(this.removeTimeout);
+    clearTimeout(this.showTimeout);
+    clearTimeout(this.restTimeout);
+    this.trigger('destroyed', this.instance);
+    this.removeFromDom();
+    this.off();
+};
+
+// Fade in the Nipple instance.
+Nipple.prototype.show = function (cb) {
+    var self = this;
+
+    if (self.options.dataOnly) {
+        return self;
+    }
+
+    clearTimeout(self.removeTimeout);
+    clearTimeout(self.showTimeout);
+    clearTimeout(self.restTimeout);
+
+    self.addToDom();
+
+    self.restCallback();
+
+    setTimeout(function () {
+        self.ui.el.style.opacity = 1;
+    }, 0);
+
+    self.showTimeout = setTimeout(function () {
+        self.trigger('shown', self.instance);
+        if (typeof cb === 'function') {
+            cb.call(this);
+        }
+    }, self.options.fadeTime);
+
+    return self;
+};
+
+// Fade out the Nipple instance.
+Nipple.prototype.hide = function (cb) {
+    var self = this;
+
+    if (self.options.dataOnly) {
+        return self;
+    }
+
+    self.ui.el.style.opacity = self.options.restOpacity;
+
+    clearTimeout(self.removeTimeout);
+    clearTimeout(self.showTimeout);
+    clearTimeout(self.restTimeout);
+
+    self.removeTimeout = setTimeout(
+        function () {
+            var display = self.options.mode === 'dynamic' ? 'none' : 'block';
+            self.ui.el.style.display = display;
+            if (typeof cb === 'function') {
+                cb.call(self);
+            }
+
+            self.trigger('hidden', self.instance);
+        },
+        self.options.fadeTime
+    );
+    self.restPosition();
+
+    return self;
+};
+
+Nipple.prototype.restPosition = function (cb) {
+    var self = this;
+    self.frontPosition = {
+        x: 0,
+        y: 0
+    };
+    var animTime = self.options.fadeTime + 'ms';
+
+    var transitStyle = {};
+    transitStyle.front = u.getTransitionStyle('transition',
+        ['top', 'left'], animTime);
+
+    var styles = {front: {}};
+    styles.front = {
+        left: self.frontPosition.x + 'px',
+        top: self.frontPosition.y + 'px'
+    };
+
+    self.applyStyles(transitStyle);
+    self.applyStyles(styles);
+
+    self.restTimeout = setTimeout(
+        function () {
+            if (typeof cb === 'function') {
+                cb.call(self);
+            }
+            self.restCallback();
+        },
+        self.options.fadeTime
+    );
+};
+
+Nipple.prototype.restCallback = function () {
+    var self = this;
+    var transitStyle = {};
+    transitStyle.front = u.getTransitionStyle('transition', 'none', '');
+    self.applyStyles(transitStyle);
+    self.trigger('rested', self.instance);
+};
+
+Nipple.prototype.resetDirection = function () {
+    // Fully rebuild the object to let the iteration possible.
+    this.direction = {
+        x: false,
+        y: false,
+        angle: false
+    };
+};
+
+Nipple.prototype.computeDirection = function (obj) {
+    var rAngle = obj.angle.radian;
+    var angle45 = Math.PI / 4;
+    var angle90 = Math.PI / 2;
+    var direction, directionX, directionY;
+
+    // Angular direction
+    //     \  UP /
+    //      \   /
+    // LEFT       RIGHT
+    //      /   \
+    //     /DOWN \
+    //
+    if (rAngle > angle45 && rAngle < (angle45 * 3)) {
+        direction = 'up';
+    } else if (rAngle > -angle45 && rAngle <= angle45) {
+        direction = 'left';
+    } else if (rAngle > (-angle45 * 3) && rAngle <= -angle45) {
+        direction = 'down';
+    } else {
+        direction = 'right';
+    }
+
+    // Plain direction
+    //    UP                 |
+    // _______               | RIGHT
+    //                  LEFT |
+    //   DOWN                |
+    if (rAngle > -angle90 && rAngle < angle90) {
+        directionX = 'left';
+    } else {
+        directionX = 'right';
+    }
+
+    if (rAngle > 0) {
+        directionY = 'up';
+    } else {
+        directionY = 'down';
+    }
+
+    if (obj.force > this.options.threshold) {
+        var oldDirection = {};
+        for (var i in this.direction) {
+            if (this.direction.hasOwnProperty(i)) {
+                oldDirection[i] = this.direction[i];
+            }
+        }
+
+        var same = {};
+
+        this.direction = {
+            x: directionX,
+            y: directionY,
+            angle: direction
+        };
+
+        obj.direction = this.direction;
+
+        for (var i in oldDirection) {
+            if (oldDirection[i] === this.direction[i]) {
+                same[i] = true;
+            }
+        }
+
+        // If all 3 directions are the same, we don't trigger anything.
+        if (same.x && same.y && same.angle) {
+            return obj;
+        }
+
+        if (!same.x || !same.y) {
+            this.trigger('plain', obj);
+        }
+
+        if (!same.x) {
+            this.trigger('plain:' + directionX, obj);
+        }
+
+        if (!same.y) {
+            this.trigger('plain:' + directionY, obj);
+        }
+
+        if (!same.angle) {
+            this.trigger('dir dir:' + direction, obj);
+        }
+    }
+    return obj;
+};
+/* global Nipple, Super */
+
+///////////////////////////
+///   THE COLLECTION    ///
+///////////////////////////
+
+function Collection (manager, options) {
+    var self = this;
+    self.nipples = [];
+    self.idles = [];
+    self.actives = [];
+    self.ids = [];
+    self.pressureIntervals = {};
+    self.manager = manager;
+    self.id = Collection.id;
+    Collection.id += 1;
+
+    // Defaults
+    self.defaults = {
+        zone: document.body,
+        multitouch: false,
+        maxNumberOfNipples: 10,
+        mode: 'dynamic',
+        position: {top: 0, left: 0},
+        catchDistance: 200,
+        size: 100,
+        threshold: 0.1,
+        color: 'white',
+        fadeTime: 250,
+        dataOnly: false,
+        restOpacity: 0.5
+    };
+
+    self.config(options);
+
+    // Overwrites
+    if (self.options.mode === 'static' || self.options.mode === 'semi') {
+        self.options.multitouch = false;
+    }
+
+    if (!self.options.multitouch) {
+        self.options.maxNumberOfNipples = 1;
+    }
+
+    self.updateBox();
+    self.prepareNipples();
+    self.bindings();
+    self.begin();
+
+    return self.nipples;
+}
+
+Collection.prototype = new Super();
+Collection.constructor = Collection;
+Collection.id = 0;
+
+Collection.prototype.prepareNipples = function () {
+    var self = this;
+    var nips = self.nipples;
+
+    // Public API Preparation.
+    nips.on = self.on.bind(self);
+    nips.off = self.off.bind(self);
+    nips.options = self.options;
+    nips.destroy = self.destroy.bind(self);
+    nips.ids = self.ids;
+    nips.id = self.id;
+    nips.processOnMove = self.processOnMove.bind(self);
+    nips.processOnEnd = self.processOnEnd.bind(self);
+    nips.get = function (id) {
+        if (id === undefined) {
+            return nips[0];
+        }
+        for (var i = 0, max = nips.length; i < max; i += 1) {
+            if (nips[i].identifier === id) {
+                return nips[i];
+            }
+        }
+        return false;
+    };
+};
+
+Collection.prototype.bindings = function () {
+    var self = this;
+    // Touch start event.
+    self.bindEvt(self.options.zone, 'start');
+    // Avoid native touch actions (scroll, zoom etc...) on the zone.
+    self.options.zone.style.touchAction = 'none';
+    self.options.zone.style.msTouchAction = 'none';
+};
+
+Collection.prototype.begin = function () {
+    var self = this;
+    var opts = self.options;
+
+    // We place our static nipple
+    // if needed.
+    if (opts.mode === 'static') {
+        var nipple = self.createNipple(
+            opts.position,
+            self.manager.getIdentifier()
+        );
+        // Add it to the dom.
+        nipple.add();
+        // Store it in idles.
+        self.idles.push(nipple);
+    }
+};
+
+// Nipple Factory
+Collection.prototype.createNipple = function (position, identifier) {
+    var self = this;
+    var scroll = u.getScroll();
+    var toPutOn = {};
+    var opts = self.options;
+
+    if (position.x && position.y) {
+        toPutOn = {
+            x: position.x -
+                (scroll.x + self.box.left),
+            y: position.y -
+                (scroll.y + self.box.top)
+        };
+    } else if (
+            position.top ||
+            position.right ||
+            position.bottom ||
+            position.left
+        ) {
+
+        // We need to compute the position X / Y of the joystick.
+        var dumb = document.createElement('DIV');
+        dumb.style.display = 'hidden';
+        dumb.style.top = position.top;
+        dumb.style.right = position.right;
+        dumb.style.bottom = position.bottom;
+        dumb.style.left = position.left;
+        dumb.style.position = 'absolute';
+
+        opts.zone.appendChild(dumb);
+        var dumbBox = dumb.getBoundingClientRect();
+        opts.zone.removeChild(dumb);
+
+        toPutOn = position;
+        position = {
+            x: dumbBox.left + scroll.x,
+            y: dumbBox.top + scroll.y
+        };
+    }
+
+    var nipple = new Nipple(self, {
+        color: opts.color,
+        size: opts.size,
+        threshold: opts.threshold,
+        fadeTime: opts.fadeTime,
+        dataOnly: opts.dataOnly,
+        restOpacity: opts.restOpacity,
+        mode: opts.mode,
+        identifier: identifier,
+        position: position,
+        zone: opts.zone,
+        frontPosition: {
+            x: 0,
+            y: 0
+        }
+    });
+
+    if (!opts.dataOnly) {
+        u.applyPosition(nipple.ui.el, toPutOn);
+        u.applyPosition(nipple.ui.front, nipple.frontPosition);
+    }
+    self.nipples.push(nipple);
+    self.trigger('added ' + nipple.identifier + ':added', nipple);
+    self.manager.trigger('added ' + nipple.identifier + ':added', nipple);
+
+    self.bindNipple(nipple);
+
+    return nipple;
+};
+
+Collection.prototype.updateBox = function () {
+    var self = this;
+    self.box = self.options.zone.getBoundingClientRect();
+};
+
+Collection.prototype.bindNipple = function (nipple) {
+    var self = this;
+    var type;
+    // Bubble up identified events.
+    var handler = function (evt, data) {
+        // Identify the event type with the nipple's id.
+        type = evt.type + ' ' + data.id + ':' + evt.type;
+        self.trigger(type, data);
+    };
+
+    // When it gets destroyed.
+    nipple.on('destroyed', self.onDestroyed.bind(self));
+
+    // Other events that will get bubbled up.
+    nipple.on('shown hidden rested dir plain', handler);
+    nipple.on('dir:up dir:right dir:down dir:left', handler);
+    nipple.on('plain:up plain:right plain:down plain:left', handler);
+};
+
+Collection.prototype.pressureFn = function (touch, nipple, identifier) {
+    var self = this;
+    var previousPressure = 0;
+    clearInterval(self.pressureIntervals[identifier]);
+    // Create an interval that will read the pressure every 100ms
+    self.pressureIntervals[identifier] = setInterval(function () {
+        var pressure = touch.force || touch.pressure ||
+            touch.webkitForce || 0;
+        if (pressure !== previousPressure) {
+            nipple.trigger('pressure', pressure);
+            self.trigger('pressure ' +
+                nipple.identifier + ':pressure', pressure);
+            previousPressure = pressure;
+        }
+    }.bind(self), 100);
+};
+
+Collection.prototype.onstart = function (evt) {
+    var self = this;
+    var opts = self.options;
+    evt = u.prepareEvent(evt);
+
+    // Update the box position
+    self.updateBox();
+
+    var process = function (touch) {
+        // If we can create new nipples
+        // meaning we don't have more active nipples than we should.
+        if (self.actives.length < opts.maxNumberOfNipples) {
+            self.processOnStart(touch);
+        }
+    };
+
+    u.map(evt, process);
+
+    // We ask upstream to bind the document
+    // on 'move' and 'end'
+    self.manager.bindDocument();
+    return false;
+};
+
+Collection.prototype.processOnStart = function (evt) {
+    var self = this;
+    var opts = self.options;
+    var indexInIdles;
+    var identifier = self.manager.getIdentifier(evt);
+    var pressure = evt.force || evt.pressure || evt.webkitForce || 0;
+    var position = {
+        x: evt.pageX,
+        y: evt.pageY
+    };
+
+    var nipple = self.getOrCreate(identifier, position);
+
+    // Update its touch identifier
+    nipple.identifier = identifier;
+
+    var process = function (nip) {
+        // Trigger the start.
+        nip.trigger('start', nip);
+        self.trigger('start ' + nip.id + ':start', nip);
+
+        nip.show();
+        if (pressure > 0) {
+            self.pressureFn(evt, nip, nip.identifier);
+        }
+        // Trigger the first move event.
+        self.processOnMove(evt);
+    };
+
+    // Transfer it from idles to actives.
+    if ((indexInIdles = self.idles.indexOf(nipple)) >= 0) {
+        self.idles.splice(indexInIdles, 1);
+    }
+
+    // Store the nipple in the actives array
+    self.actives.push(nipple);
+    self.ids.push(nipple.identifier);
+
+    if (opts.mode !== 'semi') {
+        process(nipple);
+    } else {
+        // In semi we check the distance of the touch
+        // to decide if we have to reset the nipple
+        var distance = u.distance(position, nipple.position);
+        if (distance <= opts.catchDistance) {
+            process(nipple);
+        } else {
+            nipple.destroy();
+            self.processOnStart(evt);
+            return;
+        }
+    }
+
+    return nipple;
+};
+
+Collection.prototype.getOrCreate = function (identifier, position) {
+    var self = this;
+    var opts = self.options;
+    var nipple;
+
+    // If we're in static or semi, we might already have an active.
+    if (/(semi|static)/.test(opts.mode)) {
+        // Get the active one.
+        // TODO: Multi-touche for semi and static will start here.
+        // Return the nearest one.
+        nipple = self.idles[0];
+        if (nipple) {
+            self.idles.splice(0, 1);
+            return nipple;
+        }
+
+        if (opts.mode === 'semi') {
+            // If we're in semi mode, we need to create one.
+            return self.createNipple(position, identifier);
+        }
+
+        console.warn('Coudln\'t find the needed nipple.');
+        return false;
+    }
+    // In dynamic, we create a new one.
+    nipple = self.createNipple(position, identifier);
+    return nipple;
+};
+
+Collection.prototype.processOnMove = function (evt) {
+    var self = this;
+    var opts = self.options;
+    var identifier = self.manager.getIdentifier(evt);
+    var nipple = self.nipples.get(identifier);
+
+    if (!nipple) {
+        // This is here just for safety.
+        // It shouldn't happen.
+        console.error('Found zombie joystick with ID ' + identifier);
+        self.manager.removeIdentifier(identifier);
+        return;
+    }
+
+    nipple.identifier = identifier;
+
+    var size = nipple.options.size / 2;
+    var pos = {
+        x: evt.pageX,
+        y: evt.pageY
+    };
+
+    var dist = u.distance(pos, nipple.position);
+    var angle = u.angle(pos, nipple.position);
+    var rAngle = u.radians(angle);
+    var force = dist / size;
+
+    // If distance is bigger than nipple's size
+    // we clamp the position.
+    if (dist > size) {
+        dist = size;
+        pos = u.findCoord(nipple.position, dist, angle);
+    }
+
+    nipple.frontPosition = {
+        x: pos.x - nipple.position.x,
+        y: pos.y - nipple.position.y
+    };
+
+    if (!opts.dataOnly) {
+        u.applyPosition(nipple.ui.front, nipple.frontPosition);
+    }
+
+    // Prepare event's datas.
+    var toSend = {
+        identifier: nipple.identifier,
+        position: pos,
+        force: force,
+        pressure: evt.force || evt.pressure || evt.webkitForce || 0,
+        distance: dist,
+        angle: {
+            radian: rAngle,
+            degree: angle
+        },
+        instance: nipple
+    };
+
+    // Compute the direction's datas.
+    toSend = nipple.computeDirection(toSend);
+
+    // Offset angles to follow units circle.
+    toSend.angle = {
+        radian: u.radians(180 - angle),
+        degree: 180 - angle
+    };
+
+    // Send everything to everyone.
+    nipple.trigger('move', toSend);
+    self.trigger('move ' + nipple.id + ':move', toSend);
+};
+
+Collection.prototype.processOnEnd = function (evt) {
+    var self = this;
+    var opts = self.options;
+    var identifier = self.manager.getIdentifier(evt);
+    var nipple = self.nipples.get(identifier);
+    var removedIdentifier = self.manager.removeIdentifier(nipple.identifier);
+
+    if (!nipple) {
+        return;
+    }
+
+    if (!opts.dataOnly) {
+        nipple.hide(function () {
+            if (opts.mode === 'dynamic') {
+                nipple.trigger('removed', nipple);
+                self.trigger('removed ' + nipple.id + ':removed', nipple);
+                self.manager
+                    .trigger('removed ' + nipple.id + ':removed', nipple);
+                nipple.destroy();
+            }
+        });
+    }
+
+    // Clear the pressure interval reader
+    clearInterval(self.pressureIntervals[nipple.identifier]);
+
+    // Reset the direciton of the nipple, to be able to trigger a new direction
+    // on start.
+    nipple.resetDirection();
+
+    nipple.trigger('end', nipple);
+    self.trigger('end ' + nipple.id + ':end', nipple);
+
+    // Remove identifier from our bank.
+    if (self.ids.indexOf(nipple.identifier) >= 0) {
+        self.ids.splice(self.ids.indexOf(nipple.identifier), 1);
+    }
+
+    // Clean our actives array.
+    if (self.actives.indexOf(nipple) >= 0) {
+        self.actives.splice(self.actives.indexOf(nipple), 1);
+    }
+
+    if (/(semi|static)/.test(opts.mode)) {
+        // Transfer nipple from actives to idles
+        // if we're in semi or static mode.
+        self.idles.push(nipple);
+    } else if (self.nipples.indexOf(nipple) >= 0) {
+        // Only if we're not in semi or static mode
+        // we can remove the instance.
+        self.nipples.splice(self.nipples.indexOf(nipple), 1);
+    }
+
+    // We unbind move and end.
+    self.manager.unbindDocument();
+
+    // We add back the identifier of the idle nipple;
+    if (/(semi|static)/.test(opts.mode)) {
+        self.manager.ids[removedIdentifier.id] = removedIdentifier.identifier;
+    }
+};
+
+// Remove destroyed nipple from the lists
+Collection.prototype.onDestroyed = function(evt, nipple) {
+    var self = this;
+    if (self.nipples.indexOf(nipple) >= 0) {
+        self.nipples.splice(self.nipples.indexOf(nipple), 1);
+    }
+    if (self.actives.indexOf(nipple) >= 0) {
+        self.actives.splice(self.actives.indexOf(nipple), 1);
+    }
+    if (self.idles.indexOf(nipple) >= 0) {
+        self.idles.splice(self.idles.indexOf(nipple), 1);
+    }
+    if (self.ids.indexOf(nipple.identifier) >= 0) {
+        self.ids.splice(self.ids.indexOf(nipple.identifier), 1);
+    }
+
+    // Remove the identifier from our bank
+    self.manager.removeIdentifier(nipple.identifier);
+
+    // We unbind move and end.
+    self.manager.unbindDocument();
+};
+
+// Cleanly destroy the manager
+Collection.prototype.destroy = function () {
+    var self = this;
+    self.unbindEvt(self.options.zone, 'start');
+
+    // Destroy nipples.
+    self.nipples.forEach(function(nipple) {
+        nipple.destroy();
+    });
+
+    // Clean 3DTouch intervals.
+    for (var i in self.pressureIntervals) {
+        if (self.pressureIntervals.hasOwnProperty(i)) {
+            clearInterval(self.pressureIntervals[i]);
+        }
+    }
+
+    // Notify the manager passing the instance
+    self.trigger('destroyed', self.nipples);
+    // We unbind move and end.
+    self.manager.unbindDocument();
+    // Unbind everything.
+    self.off();
+};
+/* global u, Super, Collection */
+
+///////////////////////
+///     MANAGER     ///
+///////////////////////
+
+function Manager (options) {
+    var self = this;
+    self.ids = {};
+    self.index = 0;
+    self.collections = [];
+
+    self.config(options);
+    self.prepareCollections();
+
+    // Listen for resize, to reposition every joysticks
+    var resizeTimer;
+    u.bindEvt(window, 'resize', function (evt) {
+        clearTimeout(resizeTimer);
+        resizeTimer = setTimeout(function () {
+            var pos;
+            var scroll = u.getScroll();
+            self.collections.forEach(function (collection) {
+                collection.forEach(function (nipple) {
+                    pos = nipple.el.getBoundingClientRect();
+                    nipple.position = {
+                        x: scroll.x + pos.left,
+                        y: scroll.y + pos.top
+                    };
+                });
+            });
+        }, 100);
+    });
+
+    return self.collections;
+};
+
+Manager.prototype = new Super();
+Manager.constructor = Manager;
+
+Manager.prototype.prepareCollections = function () {
+    var self = this;
+    // Public API Preparation.
+    self.collections.create = self.create.bind(self);
+    // Listen to anything
+    self.collections.on = self.on.bind(self);
+    // Unbind general events
+    self.collections.off = self.off.bind(self);
+    // Destroy everything
+    self.collections.destroy = self.destroy.bind(self);
+    // Get any nipple
+    self.collections.get = function (id) {
+        var nipple;
+        self.collections.every(function (collection) {
+            if (nipple = collection.get(id)) {
+                return false;
+            }
+            return true;
+        });
+        return nipple;
+    };
+};
+
+Manager.prototype.create = function (options) {
+    return this.createCollection(options);
+};
+
+// Collection Factory
+Manager.prototype.createCollection = function (options) {
+    var self = this;
+    var collection = new Collection(self, options);
+
+    self.bindCollection(collection);
+    self.collections.push(collection);
+
+    return collection;
+};
+
+Manager.prototype.bindCollection = function (collection) {
+    var self = this;
+    var type;
+    // Bubble up identified events.
+    var handler = function (evt, data) {
+        // Identify the event type with the nipple's identifier.
+        type = evt.type + ' ' + data.id + ':' + evt.type;
+        self.trigger(type, data);
+    };
+
+    // When it gets destroyed we clean.
+    collection.on('destroyed', self.onDestroyed.bind(self));
+
+    // Other events that will get bubbled up.
+    collection.on('shown hidden rested dir plain', handler);
+    collection.on('dir:up dir:right dir:down dir:left', handler);
+    collection.on('plain:up plain:right plain:down plain:left', handler);
+};
+
+Manager.prototype.bindDocument = function () {
+    var self = this;
+    // Bind only if not already binded
+    if (!self.binded) {
+        self.bindEvt(document, 'move')
+            .bindEvt(document, 'end');
+        self.binded = true;
+    }
+};
+
+Manager.prototype.unbindDocument = function (force) {
+    var self = this;
+    // If there are no touch left
+    // unbind the document.
+    if (!Object.keys(self.ids).length || force === true) {
+        self.unbindEvt(document, 'move')
+            .unbindEvt(document, 'end');
+        self.binded = false;
+    }
+};
+
+Manager.prototype.getIdentifier = function (evt) {
+    var id;
+    // If no event, simple increment
+    if (!evt) {
+        id = this.index;
+    } else {
+        // Extract identifier from event object.
+        // Unavailable in mouse events so replaced by latest increment.
+        id = evt.identifier === undefined ? evt.pointerId : evt.identifier;
+        if (id === undefined) {
+            id = this.latest || 0;
+        }
+    }
+
+    if (this.ids[id] === undefined) {
+        this.ids[id] = this.index;
+        this.index += 1;
+    }
+
+    // Keep the latest id used in case we're using an unidentified mouseEvent
+    this.latest = id;
+    return this.ids[id];
+};
+
+Manager.prototype.removeIdentifier = function (identifier) {
+    var removed = {};
+    for (var id in this.ids) {
+        if (this.ids[id] === identifier) {
+            removed.id = id;
+            removed.identifier = this.ids[id];
+            delete this.ids[id];
+            break;
+        }
+    }
+    return removed;
+};
+
+Manager.prototype.onmove = function (evt) {
+    var self = this;
+    self.onAny('move', evt);
+    return false;
+};
+
+Manager.prototype.onend = function (evt) {
+    var self = this;
+    self.onAny('end', evt);
+    return false;
+};
+
+Manager.prototype.onAny = function (which, evt) {
+    var self = this;
+    var id;
+    var processFn = 'processOn' + which.charAt(0).toUpperCase() +
+        which.slice(1);
+    evt = u.prepareEvent(evt);
+    var processColl = function (e, id, coll) {
+        if (coll.ids.indexOf(id) >= 0) {
+            coll[processFn](e);
+            // Mark the event to avoid cleaning it later.
+            e._found_ = true;
+        }
+    };
+    var processEvt = function (e) {
+        id = self.getIdentifier(e);
+        u.map(self.collections, processColl.bind(null, e, id));
+        // If the event isn't handled by any collection,
+        // we need to clean its identifier.
+        if (!e._found_) {
+            self.removeIdentifier(id);
+        }
+    };
+
+    u.map(evt, processEvt);
+
+    return false;
+};
+
+// Cleanly destroy the manager
+Manager.prototype.destroy = function () {
+    var self = this;
+    self.unbindDocument(true);
+    self.ids = {};
+    self.index = 0;
+    self.collections.forEach(function(collection) {
+        collection.destroy();
+    });
+    self.off();
+};
+
+// When a collection gets destroyed
+// we clean behind.
+Manager.prototype.onDestroyed = function (evt, coll) {
+    var self = this;
+    if (self.collections.indexOf(coll) < 0) {
+        return false;
+    }
+    self.collections.splice(self.collections.indexOf(coll), 1);
+};
+var factory = new Manager();
+return {
+    create: function (options) {
+        return factory.create(options);
+    },
+    factory: factory
+};
+
+});

File diff suppressed because it is too large
+ 0 - 0
support/client/lib/vwf/view/lib/nipplejs/nipplejs.min.js


+ 1 - 1
support/proxy/vwf.example.com/aframe/avatar.js

@@ -312,7 +312,7 @@ this.setSmallVideoHead = function(val){
 
 this.setVideoTexture = function(val){
     console.log(val);
-    this.setSmallVideoHead();
+   // this.setSmallVideoHead();
     this.avatarNode.myHead.visual.color = "white";
     this.avatarNode.myHead.visual.src = '#temp';
     this.avatarNode.myHead.visual.src = '#'+val;

Some files were not shown because too many files changed in this diff