├── client
├── views
│ ├── footer.html
│ ├── home.html
│ ├── demo.html
│ ├── connect.html
│ └── docs.html
├── .gitignore
├── assets
│ ├── footer_lodyas.png
│ ├── GitHub-Mark-32px.png
│ ├── GitHub-Mark-64px.png
│ ├── congruent_outline.png
│ ├── GitHub-Mark-Light-32px.png
│ ├── GitHub-Mark-Light-64px.png
│ ├── app.css
│ ├── normalize.css
│ └── skeleton.css
├── demo
│ ├── assets
│ │ ├── peach-gradient-1.jpg
│ │ ├── tex
│ │ │ └── shadow-circle.png
│ │ ├── 321103__nsstudios__blip1.wav
│ │ ├── tree1.dae
│ │ └── tree2.dae
│ └── index.html
├── services
│ ├── index.js
│ └── proxy-service.js
├── controllers
│ ├── index.js
│ ├── home-ctrl.js
│ ├── docs-ctrl.js
│ ├── nav-ctrl.js
│ └── connect-ctrl.js
├── app.js
├── lib
│ ├── keyboard-listener.js
│ ├── listener.js
│ ├── gamepad-listener.js
│ ├── proxy-controls-client.js
│ └── keyboard.polyfill.js
└── index.html
├── .gitignore
├── .jshintrc
├── index.js
├── README.md
├── LICENSE
├── package.json
└── server
└── proxy-controls-server.js
/client/views/footer.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/client/.gitignore:
--------------------------------------------------------------------------------
1 | bundle.*
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | bower_components/
3 | .DS_Store
4 | .gh-pages
5 | .env
6 |
--------------------------------------------------------------------------------
/client/assets/footer_lodyas.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/donmccurdy/proxy-controls-server/master/client/assets/footer_lodyas.png
--------------------------------------------------------------------------------
/client/assets/GitHub-Mark-32px.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/donmccurdy/proxy-controls-server/master/client/assets/GitHub-Mark-32px.png
--------------------------------------------------------------------------------
/client/assets/GitHub-Mark-64px.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/donmccurdy/proxy-controls-server/master/client/assets/GitHub-Mark-64px.png
--------------------------------------------------------------------------------
/client/assets/congruent_outline.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/donmccurdy/proxy-controls-server/master/client/assets/congruent_outline.png
--------------------------------------------------------------------------------
/client/demo/assets/peach-gradient-1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/donmccurdy/proxy-controls-server/master/client/demo/assets/peach-gradient-1.jpg
--------------------------------------------------------------------------------
/client/assets/GitHub-Mark-Light-32px.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/donmccurdy/proxy-controls-server/master/client/assets/GitHub-Mark-Light-32px.png
--------------------------------------------------------------------------------
/client/assets/GitHub-Mark-Light-64px.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/donmccurdy/proxy-controls-server/master/client/assets/GitHub-Mark-Light-64px.png
--------------------------------------------------------------------------------
/client/demo/assets/tex/shadow-circle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/donmccurdy/proxy-controls-server/master/client/demo/assets/tex/shadow-circle.png
--------------------------------------------------------------------------------
/client/services/index.js:
--------------------------------------------------------------------------------
1 | var app = require('angular').module('proxyControlsApp');
2 |
3 | app.factory('ProxyService', require('./proxy-service'));
4 |
--------------------------------------------------------------------------------
/client/demo/assets/321103__nsstudios__blip1.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/donmccurdy/proxy-controls-server/master/client/demo/assets/321103__nsstudios__blip1.wav
--------------------------------------------------------------------------------
/client/controllers/index.js:
--------------------------------------------------------------------------------
1 | var app = require('angular').module('proxyControlsApp');
2 |
3 | app
4 | .controller('ConnectCtrl', require('./connect-ctrl'))
5 | .controller('DocsCtrl', require('./docs-ctrl'))
6 | .controller('HomeCtrl', require('./home-ctrl'))
7 | .controller('NavCtrl', require('./nav-ctrl'));
8 |
--------------------------------------------------------------------------------
/client/services/proxy-service.js:
--------------------------------------------------------------------------------
1 | var ProxyControlsClient = require('../lib/proxy-controls-client');
2 |
3 | module.exports = function (RESOURCES) {
4 | return {
5 | get: function (pairCode) {
6 | return new ProxyControlsClient({url: RESOURCES.SOCKET_PATH, pairCode: pairCode});
7 | }
8 | };
9 | };
10 |
--------------------------------------------------------------------------------
/client/controllers/home-ctrl.js:
--------------------------------------------------------------------------------
1 | module.exports = function ($scope, $window, RESOURCES) {
2 | $scope.demoUrl = RESOURCES.DEMO_URL;
3 | $scope.isMobile = $window.matchMedia('(max-width: 550px)').matches;
4 |
5 | $window.document.body.classList.add('bg-dark');
6 | $scope.$on('$destroy', function () {
7 | $window.document.body.classList.remove('bg-dark');
8 | });
9 | };
10 |
--------------------------------------------------------------------------------
/client/controllers/docs-ctrl.js:
--------------------------------------------------------------------------------
1 | module.exports = function ($scope, $location, $anchorScroll) {
2 |
3 | var originalOffset = $anchorScroll.yOffset;
4 | $anchorScroll.yOffset = 100;
5 | $anchorScroll();
6 |
7 | $scope.goHash = function (anchor) {
8 | $location.hash(anchor);
9 | $anchorScroll();
10 | };
11 |
12 | $scope.$on('$destroy', function () {
13 | $anchorScroll.yOffset = originalOffset;
14 | });
15 | };
16 |
--------------------------------------------------------------------------------
/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "globals": {},
3 | "bitwise": false,
4 | "browser": true,
5 | "eqeqeq": true,
6 | "esnext": true,
7 | "expr": true,
8 | "forin": true,
9 | "immed": true,
10 | "latedef": "nofunc",
11 | "laxbreak": true,
12 | "maxlen": 100,
13 | "newcap": true,
14 | "noarg": true,
15 | "node": true,
16 | "noempty": true,
17 | "noyield": true,
18 | "quotmark": "single",
19 | "smarttabs": false,
20 | "trailing": true,
21 | "undef": true,
22 | "unused": true,
23 | "white": false
24 | }
25 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | var fs = require('fs'),
2 | dotenv = require('dotenv'),
3 | ProxyControlsServer = require('./server/proxy-controls-server');
4 |
5 | dotenv.config({silent: true});
6 |
7 | if (process.env.SSL_PORT) {
8 | new ProxyControlsServer({
9 | port: process.env.PORT,
10 | sslPort: process.env.SSL_PORT,
11 | key: fs.readFileSync(process.env.SSL_KEY_PATH),
12 | cert: fs.readFileSync(process.env.SSL_CERT_PATH),
13 | });
14 | } else {
15 | new ProxyControlsServer({
16 | port: process.env.PORT || process.env.npm_package_config_port
17 | });
18 | }
19 |
20 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Proxy Controls Server
2 |
3 | 
4 | [](https://github.com/donmccurdy/proxy-controls-server/blob/master/LICENSE)
5 |
6 | Service to proxy keyboard/gamepad controls between devices, peer-to-peer, over WebRTC.
7 |
8 | ## Overview
9 |
10 | ```
11 | .
12 | ├── client
13 | │ ├── assets
14 | │ ├── controllers
15 | │ ├── lib
16 | │ ├── services
17 | │ └── views
18 | └── server
19 | ```
20 |
21 | ## Dependencies
22 |
23 | This project relies on the [SocketPeer](https://github.com/cvan/socketpeer) library to manage WebRTC and WebSocket connections.
24 |
25 | ## Notes
26 |
27 | Two optional dependencies for `ws`, `bufferutil` and `utf-8-validate`, are included. [Both improve performance in certain conditions](https://github.com/websockets/ws#opt-in-for-performance).
28 |
--------------------------------------------------------------------------------
/client/app.js:
--------------------------------------------------------------------------------
1 | var angular = require('angular');
2 |
3 | angular.module('proxyControlsApp', [
4 | require('angular-route'),
5 | require('angular-sanitize'),
6 | require('angular-touch')
7 | ])
8 | .constant('RESOURCES', {
9 | DEMO_URL: '/demo',
10 | SOCKET_PATH: location.protocol + '//' + location.host + '/socketpeer/'
11 | })
12 | .config(function ($routeProvider) {
13 | $routeProvider
14 | .when('/', {
15 | templateUrl: 'views/home.html',
16 | controller: 'HomeCtrl'
17 | })
18 | .when('/docs', {
19 | templateUrl: 'views/docs.html',
20 | controller: 'DocsCtrl'
21 | })
22 | .when('/connect', {
23 | templateUrl: 'views/connect.html',
24 | controller: 'ConnectCtrl'
25 | })
26 | .otherwise({redirectTo: '/'});
27 | }).config(['$locationProvider', function ($locationProvider) {
28 | $locationProvider.hashPrefix('');
29 | }]);
30 |
31 | require('./controllers');
32 | require('./services');
33 |
--------------------------------------------------------------------------------
/client/controllers/nav-ctrl.js:
--------------------------------------------------------------------------------
1 | module.exports = function ($scope, $document, $window, $element, $location) {
2 |
3 | // Navigation drawer state.
4 | $scope.navOpen = false;
5 |
6 | // Check if given path is active.
7 | $scope.isActive = function (path) { return path === $location.path(); };
8 |
9 | // Close navigation if page changes.
10 | $scope.$on('$locationChangeStart', function() {
11 | $window.scroll(0, 0);
12 | $scope.navOpen = false;
13 | });
14 |
15 | var closeNav = function (e) {
16 | if ($scope.navOpen) {
17 | $scope.$apply(function () { $scope.navOpen = false; });
18 | e.preventDefault();
19 | }
20 | };
21 |
22 | var isolateNav = function (e) { e.stopPropagation(); };
23 |
24 | // Close navigation if user clicks outside nav.
25 | $element.on('click', isolateNav);
26 | $element.on('touchstart', isolateNav);
27 | $document.on('touchstart', closeNav);
28 | $document.on('click', closeNav);
29 |
30 | $scope.$on('$destroy', function () {
31 | $document.off('touchstart', closeNav);
32 | $document.off('click', closeNav);
33 | });
34 |
35 | };
36 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2019 Don McCurdy
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/client/lib/keyboard-listener.js:
--------------------------------------------------------------------------------
1 | var Listener = require('./listener'),
2 | util = require('util');
3 |
4 | require('./keyboard.polyfill');
5 |
6 | function KeyboardListener () {
7 | Listener.call(this);
8 |
9 | this.title = 'Keyboard';
10 | this.type = 'keyboard';
11 | this.component = 'keyboard-controls';
12 | this.github = 'https://github.com/donmccurdy/aframe-keyboard-controls';
13 |
14 | this.keys = {};
15 | }
16 |
17 | util.inherits(KeyboardListener, Listener);
18 |
19 | KeyboardListener.prototype.bind = function () {
20 | Listener.prototype.bind.call(this);
21 |
22 | this.__listeners.keydown = this.onKeydown.bind(this);
23 | this.__listeners.keyup = this.onKeyup.bind(this);
24 |
25 | document.addEventListener('keydown', this.__listeners.keydown);
26 | document.addEventListener('keyup', this.__listeners.keyup);
27 | };
28 |
29 | KeyboardListener.prototype.onKeydown = function (e) {
30 | if (this.keys[e.code]) return;
31 | this.keys[e.code] = true;
32 | this.emit(this.type, {type: this.type, state: this.keys});
33 | };
34 |
35 | KeyboardListener.prototype.onKeyup = function (e) {
36 | if (!this.keys[e.code]) return;
37 | delete this.keys[e.code];
38 | this.emit(this.type, {type: this.type, state: this.keys});
39 | };
40 |
41 | module.exports = KeyboardListener;
42 |
--------------------------------------------------------------------------------
/client/lib/listener.js:
--------------------------------------------------------------------------------
1 | var EventEmitter = require('events'),
2 | util = require('util');
3 |
4 | function Listener () {
5 | EventEmitter.call(this);
6 |
7 | /** @type {string:function} Event types and callbacks for document events. */
8 | this.__listeners = {};
9 |
10 | /** @type {string} User-facing label for the listener. */
11 | this.title = '';
12 |
13 | /** @type {string} Event type for the listener. */
14 | this.type = '';
15 |
16 | /** @type {boolean} Enabled/disabled state. */
17 | this.enabled = true;
18 | }
19 |
20 | util.inherits(Listener, EventEmitter);
21 |
22 | Listener.prototype.emit = function () {
23 | if (!this.enabled) return;
24 | EventEmitter.prototype.emit.apply(this, arguments);
25 | };
26 |
27 | Listener.prototype.bind = function () {};
28 |
29 | Listener.prototype.unbind = function () {
30 | for (var event in this.__listeners) {
31 | if (this.__listeners.hasOwnProperty(event)) {
32 | document.removeEventListener(event, this.__listeners[event]);
33 | delete this.__listeners[event];
34 | }
35 | }
36 | };
37 |
38 | Listener.prototype.destroy = function () {
39 | this.unbind();
40 | };
41 |
42 | Listener.prototype.isEnabled = function () {
43 | return this.enabled;
44 | };
45 |
46 | Listener.prototype.setEnabled = function (enabled) {
47 | this.enabled = !!enabled;
48 | };
49 |
50 | module.exports = Listener;
51 |
--------------------------------------------------------------------------------
/client/views/home.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Mobile WebVR
8 | + Interactivity
9 |
10 |
11 |
12 |
13 | Connect input devices from your desktop to your mobile phone with WebRTC.
14 |
15 |
16 |
20 |
21 |
22 |
23 |
24 | Developing with A-Frame ?
25 | Use the
26 |
27 | proxy-controls
32 |
33 | component in your scene. Your demo will use this site as the backend (it’s free), so
34 | there’s no need to run your own server.
35 | Read more .
36 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/client/lib/gamepad-listener.js:
--------------------------------------------------------------------------------
1 | var Listener = require('./listener'),
2 | util = require('util');
3 |
4 | function GamepadListener () {
5 | Listener.call(this);
6 |
7 | this.title = 'Gamepad';
8 | this.type = 'gamepad';
9 | this.component = 'gamepad-controls';
10 | this.github = 'https://github.com/donmccurdy/aframe-gamepad-controls';
11 | }
12 |
13 | util.inherits(GamepadListener, Listener);
14 |
15 | GamepadListener.prototype.bind = function () {
16 | // TODO - implement unbind()
17 | if (this.__bound) return;
18 | this.__bound = true;
19 |
20 | Listener.prototype.bind.call(this);
21 |
22 | var publish = function () {
23 | var gamepads = [];
24 | for (var i = 0; i < 4; i++) {
25 | var gamepad = navigator.getGamepads()[i];
26 | if (gamepad) {
27 | gamepads.push(cloneGamepad(gamepad));
28 | }
29 | }
30 | if (gamepads.length) {
31 | this.emit(this.type, {type: this.type, state: gamepads});
32 | }
33 | window.requestAnimationFrame(publish);
34 | }.bind(this);
35 |
36 | var cloneGamepad = function (gamepad) {
37 | var clone = {
38 | axes: gamepad.axes,
39 | buttons: [],
40 | connected: gamepad.connected,
41 | id: gamepad.id,
42 | index: gamepad.index,
43 | mapping: gamepad.mapping,
44 | timestamp: gamepad.timestamp,
45 | };
46 |
47 | for (var i = 0; i < gamepad.buttons.length; i++) {
48 | clone.buttons.push({
49 | pressed: gamepad.buttons[i].pressed,
50 | value: gamepad.buttons[i].value
51 | });
52 | }
53 |
54 | return clone;
55 | };
56 |
57 | window.requestAnimationFrame(publish);
58 | };
59 |
60 | GamepadListener.prototype.unbind = function () {
61 | // TODO
62 | };
63 |
64 | module.exports = GamepadListener;
65 |
--------------------------------------------------------------------------------
/client/controllers/connect-ctrl.js:
--------------------------------------------------------------------------------
1 | var angular = require('angular');
2 |
3 | var INTERVAL = 1000,
4 | DEFAULT_SCOPE = {
5 | pairCode: '',
6 | server: {connected: false},
7 | peer: {protocol: '', connected: false, latency: 0},
8 | listeners: [],
9 | protocolLabels: {
10 | rtc: 'WebRTC',
11 | socket: 'WebSocket'
12 | }
13 | };
14 |
15 | module.exports = function ($scope, $route, $interval, ProxyService, RESOURCES) {
16 | var proxyService = null;
17 |
18 | $scope.demoUrl = RESOURCES.DEMO_URL;
19 |
20 | angular.merge($scope, angular.copy(DEFAULT_SCOPE));
21 |
22 | $scope.connect = function (pairCode) {
23 | if (proxyService) {
24 | throw new Error('Already connected to a client.');
25 | } else if (pairCode) {
26 | $scope.pairCode = pairCode;
27 | proxyService = ProxyService.get(pairCode);
28 | }
29 | };
30 |
31 | $scope.disconnect = function () {
32 | proxyService.destroy();
33 | proxyService = null;
34 | this.reset();
35 | }.bind(this);
36 |
37 | this.reset = function () {
38 | angular.merge($scope, angular.copy(DEFAULT_SCOPE));
39 | };
40 |
41 | // Poll for changes, because events emitted by proxy service and underlying
42 | // SocketPeer object aren't enough to indicate when server connection status
43 | // has changed.
44 | var intervalPromise = $interval(function () {
45 | if (!proxyService) return;
46 | $scope.server.connected = proxyService.isServerConnected();
47 | $scope.peer.protocol = proxyService.getPeerProtocol();
48 | $scope.peer.connected = proxyService.isPeerConnected();
49 | $scope.peer.latency = proxyService.getPeerLatency();
50 | $scope.listeners = proxyService.listeners;
51 | }, INTERVAL);
52 |
53 | $scope.$on('$destroy', function () {
54 | if (proxyService) $scope.disconnect();
55 | $interval.cancel(intervalPromise);
56 | });
57 | };
58 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "proxy-controls-server",
3 | "version": "0.0.1",
4 | "description": "Service to proxy keyboard/gamepad controls between devices, peer-to-peer, over WebRTC.",
5 | "main": "server/proxy-controls-server.js",
6 | "browser": "client/lib/proxy-controls-client.js",
7 | "engines": {
8 | "node": "6"
9 | },
10 | "config": {
11 | "port": 3000
12 | },
13 | "now": {
14 | "alias": "proxy-controls.donmccurdy.com",
15 | "public": true
16 | },
17 | "scripts": {
18 | "start": "node index.js",
19 | "build": "browserify client/app.js -o client/bundle.js && gzip -f client/bundle.js ",
20 | "dev": "watchify client/app.js -o client/bundle.js -d & nodemon index.js",
21 | "test": "karma start ./tests/karma.conf.js",
22 | "postversion": "git push && git push --tags",
23 | "deploy": "npm run build && now"
24 | },
25 | "repository": {
26 | "type": "git",
27 | "url": "git+https://github.com/donmccurdy/proxy-controls-server.git"
28 | },
29 | "keywords": [
30 | "vr",
31 | "webvr",
32 | "webrtc",
33 | "gamepad",
34 | "keyboard",
35 | "aframe",
36 | "aframe-vr",
37 | "mozvr"
38 | ],
39 | "author": "Don McCurdy ",
40 | "license": "MIT",
41 | "bugs": {
42 | "url": "https://github.com/donmccurdy/proxy-controls-server/issues"
43 | },
44 | "homepage": "https://github.com/donmccurdy/proxy-controls-server#readme",
45 | "dependencies": {
46 | "angular": "^1.4.8",
47 | "angular-route": "^1.4.8",
48 | "angular-sanitize": "^1.4.8",
49 | "angular-touch": "^1.4.8",
50 | "browserify": "^12.0.1",
51 | "dotenv": "^2.0.0",
52 | "koa": "^1.1.2",
53 | "koa-cors": "0.0.16",
54 | "koa-force-ssl": "0.0.5",
55 | "koa-route": "^2.4.2",
56 | "koa-static": "^2.0.0",
57 | "moniker": "^0.1.2",
58 | "socketpeer": "donmccurdy/socketpeer#fork-master"
59 | },
60 | "optionalDependencies": {
61 | "bufferutil": "^1.2.1",
62 | "utf-8-validate": "^1.2.1"
63 | },
64 | "devDependencies": {
65 | "envify": "^3.4.0",
66 | "nodemon": "^1.8.1",
67 | "watchify": "^3.7.0"
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/client/views/demo.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
8 |
13 |
14 |
15 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
34 |
35 |
36 |
39 |
40 |
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/server/proxy-controls-server.js:
--------------------------------------------------------------------------------
1 | var assert = require('assert'),
2 | http = require('http'),
3 | https = require('https'),
4 | Koa = require('koa'),
5 | cors = require('koa-cors'),
6 | route = require('koa-route'),
7 | resources = require('koa-static'),
8 | forceSSL = require('koa-force-ssl'),
9 | SocketPeerServer = require('socketpeer'),
10 | moniker = require('moniker');
11 |
12 | /**
13 | * Server to proxy keyboard/gamepad controls between devices, peer-to-peer,
14 | * over WebRTC. Uses WebSockets as a fallback connection.
15 | *
16 | * Additionally, the ProxyControlsServer is responsible for serving the client
17 | * UI for the host machine, which receives user input to be sent to the remote
18 | * client / viewing device.
19 | *
20 | * @param {Object} options Server configuration options.
21 | */
22 | function ProxyControlsServer (options) {
23 | /** @type {Koa} Koa application, to serve client UI and AJAX endpoints. */
24 | this.app = new Koa();
25 |
26 | /** @type {http.Server} Server, to support both Koa and SocketPeerServer. */
27 | this.server = null;
28 |
29 | if (options.sslPort) {
30 | assert(options.key, 'key required for SSL.');
31 | assert(options.cert, 'cert required for SSL.');
32 |
33 | this.app.use(forceSSL());
34 | http.createServer(this.app.callback()).listen(options.port);
35 | this.server = https.createServer({
36 | key: options.key,
37 | cert: options.cert
38 | }, this.app.callback()).listen(options.sslPort);
39 | } else {
40 | this.server = this.app.listen(options.port);
41 | }
42 |
43 | this.app
44 | .use(cors({origin: true}))
45 | .use(resources('client'))
46 | .use(route.get('/ajax/nearby', this.routeNearby()))
47 | .use(route.get('/ajax/pair-code', this.routePairCode()));
48 |
49 | /** @type {SocketPeerServer} WebSocket / WebRTC connection broker. */
50 | this.socketServer = new SocketPeerServer({
51 | httpServer: this.server,
52 | serveLibrary: false
53 | });
54 |
55 | console.info('ProxyControlsServer listening on port %d.', options.sslPort || options.port);
56 | }
57 |
58 | /**
59 | * Suggests nearby peers, assuming same public IP.
60 | */
61 | ProxyControlsServer.prototype.routeNearby = function () {
62 | return function *() {
63 | // TODO - Implement.
64 | this.body = {count: 0, peers: []};
65 | };
66 | };
67 |
68 | /**
69 | * Returns a unique pair code for client.
70 | */
71 | ProxyControlsServer.prototype.routePairCode = function () {
72 | return function *() {
73 | // TODO - Verify that pair code is not already in the waiting pool, and that
74 | // the code hasn't been assigned to another client in the last ~60s.
75 | this.body = {pairCode: moniker.choose()};
76 | };
77 | };
78 |
79 | module.exports = ProxyControlsServer;
80 |
--------------------------------------------------------------------------------
/client/views/connect.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
Get Started
5 |
6 |
With this tab in focus, keyboard and gamepad events can be sent to a remote client.
7 |
8 |
9 |
Notice
10 |
11 | You're joining as a host device (the one that has a keyboard
12 | or gamepad already connected) and appear to be using a mobile phone.
13 | Did you mean to open a demo VR scene ,
14 | or to visit this page from your PC?
15 |
16 |
17 |
18 |
20 |
Pairing
21 |
Look for your pair code on the remote device, then enter it here to connect.
22 |
23 |
24 |
30 |
31 |
32 | Connect
34 |
35 |
36 |
37 |
Waiting...
38 |
39 | Waiting for remote device to connect with code “{{ pairCode }} ”.
40 |
41 |
42 |
43 |
Connected.
44 |
45 | Okay, the input devices(s) selected below are now connected to your remote device. Have fun.
46 |
47 |
48 |
49 |
50 |
51 |
53 |
Connection
54 |
55 |
56 |
57 |
58 |
59 | Server Status
60 |
61 |
62 | {{ server.connected ? 'Connected' : 'Disconnected' }}
63 |
64 |
65 |
66 |
67 | Remote Device
68 |
69 |
70 | {{ peer.connected ? 'Connected' : 'Disconnected' }}
71 |
72 |
73 |
74 |
75 | Protocol
76 |
77 |
80 | {{ peer.protocol ? protocolLabels[peer.protocol] : 'Disconnected' }}
81 |
82 |
83 |
84 |
85 | Latency
86 | {{ peer.latency || '∞' }}ms
87 |
88 |
89 |
90 |
91 |
92 |
93 |
95 |
96 |
Input Devices
97 |
98 |
101 | {{ listener.isEnabled() ? 'Disable' : 'Enable' }}
102 |
103 |
{{ listener.title }}
104 |
108 |
109 |
110 |
111 |
112 |
--------------------------------------------------------------------------------
/client/demo/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | ProxyControls • Demo
6 |
7 |
8 |
9 |
10 |
43 |
44 |
45 |
46 |
50 |
57 |
58 |
59 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
78 |
79 |
80 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
Move around with WASD keys, ←↑→↓, or a USB gamepad.
90 |
Pair code: “ ”
91 |
92 |
93 |
94 |
107 |
108 |
109 |
110 |
--------------------------------------------------------------------------------
/client/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
7 |
8 |
9 | ProxyControls.js
10 |
11 |
12 |
13 |
15 |
16 |
17 |
18 |
20 |
21 |
22 |
24 |
25 |
26 |
27 |
28 |
30 |
31 |
32 |
33 |
35 |
44 |
45 |
47 |
48 |
49 |
50 |
51 |
52 |
54 |
74 |
75 |
77 |
78 |
79 |
81 |
82 |
83 |
84 |
87 |
90 |
93 |
94 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
--------------------------------------------------------------------------------
/client/views/docs.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
Docs
5 |
6 |
13 |
14 |
15 | Overview
16 |
17 |
18 | ProxyControls shares events from input devices (keyboard, gamepad, etc.)
19 | with a remote device. While viewing a WebVR site on your phone with Google
20 | Cardboard, you can use a USB gamepad — connected to your PC — to
21 | move around.
22 |
23 |
24 |
25 | This project works great with Mozilla’s new WebVR library, A-Frame .
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 | Host Device
34 |
35 |
36 | For your host (the machine connected directly to the input device), this
37 | website is all you need:
38 |
39 |
40 | › Connect as host
41 |
42 |
43 |
44 |
45 |
46 |
47 | Remote Device + A-Frame VR
48 |
49 |
50 | For your remote device (e.g. phone) use a proxy-controls
51 | component to receive input events:
52 |
53 |
54 |
67 |
68 |
69 | You’ll also need an input device component to apply
70 | those events and manipulate the scene. Here are a few client packages,
71 | designed for A-Frame:
72 |
73 |
74 |
101 |
102 | Example:
103 |
104 | <a-scene proxy-controls >
105 | <a-cube></a-cube>
106 | <a-entity camera
107 | gamepad-controls
108 | keyboard-controls >
109 | </a-entity>
110 | </a-scene>
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 | Remote Device, without A-Frame
119 |
120 |
121 | There’s no official pre-packaged support for ProxyControls outside of the
122 | A-Frame environment yet. Feel free to build your own, or
123 | open an issue on GitHub
124 | if you’re interested in discussing an idea.
125 |
126 |
127 |
128 |
129 |
130 |
131 | WebRTC + Browser Compatibility
132 |
133 |
134 | ProxyControls uses WebRTC's peer-to-peer DataChannel API to reduce
135 | connection latency. The WebRTC spec is not finalized, and
136 | not yet
137 | supported in all browsers . When WebRTC is not available, ProxyControls
138 | will automatically switch to WebSockets.
139 |
140 |
141 |
142 |
143 | Browser Status (Jan. 2016)
144 |
145 |
146 | Chrome WebRTC + WebSockets
147 | Firefox WebRTC + WebSockets
148 | Opera WebRTC + WebSockets
149 | Safari / iOS WebSockets
150 | Edge WebSockets
151 | IE 11 WebSockets
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
--------------------------------------------------------------------------------
/client/lib/proxy-controls-client.js:
--------------------------------------------------------------------------------
1 | var SocketPeer = require('socketpeer'),
2 | EventEmitter = require('events'),
3 | util = require('util'),
4 | KeyboardListener = require('./keyboard-listener'),
5 | GamepadListener = require('./gamepad-listener');
6 |
7 | /**
8 | * Polling configuration.
9 | *
10 | * The purpose of polling on WebSockets is keep the socket from closing on
11 | * certain hosts, e.g. Heroku. A long interval (1s) is fine for this. When
12 | * polling on WebRTC, the goal is to keep the DataChannel primed – latency
13 | * seems to degrade when data is sent less frequently. WebRTC requires higher-
14 | * frequency polling.
15 | *
16 | * In both cases, polling also allows us to measure average latency.
17 | *
18 | * @type {string: {BUFFER_SIZE: number, INTERVAL: number}}
19 | */
20 | var POLLING = {
21 | rtc: {BUFFER_SIZE: 50, INTERVAL: 20},
22 | socket: {BUFFER_SIZE: 5, INTERVAL: 1000}
23 | };
24 |
25 | /**
26 | * UI controller for client running on host machine. Records user input and
27 | * forwards events to remote client / viewer application.
28 | *
29 | * Options:
30 | * - url: (required) URL of ProxyControlsServer instance.
31 | * - pairCode: (required) Identifier for this client.
32 | *
33 | * @param {Object} options Client configuration.
34 | */
35 | var ProxyControlsClient = function (options) {
36 | EventEmitter.call(this);
37 |
38 | /** @type {SocketPeer} WebRTC connection. */
39 | this.peer = new SocketPeer({
40 | url: options.url,
41 | pairCode: options.pairCode,
42 | socketFallback: true
43 | });
44 |
45 | /** @type {string:Listener} Listeners bound to document events. */
46 | this.listeners = [
47 | new KeyboardListener(),
48 | new GamepadListener()
49 | ];
50 |
51 | /** @type {string:boolean} Keyboard state, [key]->true. */
52 | this.keys = {};
53 |
54 | /** @type {Array} Circular array of recent ping measurements. */
55 | this.pingList = [];
56 |
57 | /** @type {number} Index at which next polling results are inserted. */
58 | this.pingListIndex = 0;
59 |
60 | /** @type {number} Interval ID for ping / latency polling. */
61 | this.pingIntervalID = 0;
62 |
63 | this.onConnect = this.onConnect.bind(this);
64 | this.onDisconnect = this.onDisconnect.bind(this);
65 | this.onClose = this.onClose.bind(this);
66 | this.onUpgrade = this.onUpgrade.bind(this);
67 | this.onDowngrade = this.onDowngrade.bind(this);
68 | this.onConnectError = this.onConnectError.bind(this);
69 | this.onConnectTimeout = this.onConnectTimeout.bind(this);
70 | this.onData = this.onData.bind(this);
71 | this.onError = this.onError.bind(this);
72 |
73 | this.bindEvents();
74 | };
75 |
76 | util.inherits(ProxyControlsClient, EventEmitter);
77 |
78 | /**
79 | * Initializes SocketPeer connection with broker server, and begins listening
80 | * for peer connections.
81 | */
82 | ProxyControlsClient.prototype.bindEvents = function () {
83 | this.peer.on('connect', this.onConnect);
84 | this.peer.on('connect_error', this.onConnectError);
85 | this.peer.on('connect_timeout', this.onConnectTimeout);
86 | this.peer.on('upgrade', this.onUpgrade);
87 | this.peer.on('error', this.onError);
88 | this.peer.on('data', this.onData);
89 | this.peer.on('disconnect', this.onDisconnect);
90 | this.peer.on('close', this.onClose);
91 |
92 | var self = this;
93 | this.listeners.forEach(function (listener) {
94 | listener.on(listener.type, function (e) {
95 | self.peer.send(e);
96 | self.emit(listener.type, e);
97 | console.log('publish(%s)', JSON.stringify(e, null, 2));
98 | });
99 | });
100 | };
101 |
102 | /* Initialization
103 | –––––––––––––––––––––––––––––––––––––––––––––––––– */
104 |
105 | /**
106 | * Begins sending pings to poll for latency, and also to keep the DataChannel
107 | * or WebSocket active. Resets polling, if it has already begun.
108 | *
109 | * Purpose: Some hosts (e.g. Heroku) will close WebSockets if they are
110 | * inactive for too long. On the other hand, WebRTC DataChannels appear to
111 | * perform better (lower average latency) if they're kept active. So in both
112 | * cases, we want to keep data moving through.
113 | */
114 | ProxyControlsClient.prototype.resetPings = function () {
115 | // Cancel existing polling.
116 | if (this.pingIntervalID) {
117 | clearInterval(this.pingIntervalID);
118 | }
119 |
120 | // Only poll while connected.
121 | var protocol = this.getPeerProtocol();
122 | if (!protocol) return;
123 |
124 | this.pingListIndex = 0;
125 | this.pingList = [];
126 |
127 | // Ping remote peer at regular intervals.
128 | this.pingIntervalID = setInterval(function () {
129 | if (this.getPeerProtocol()) {
130 | this.peer.send({type: 'ping', timestamp: Date.now()});
131 | } else {
132 | this.resetPings();
133 | }
134 | }.bind(this), POLLING[protocol].INTERVAL);
135 |
136 | console.info('Now polling every %dms', POLLING[protocol].INTERVAL);
137 | };
138 |
139 | /**
140 | * Removes all event bindings and destroys dependencies.
141 | */
142 | ProxyControlsClient.prototype.destroy = function () {
143 | if (this.pingIntervalID) {
144 | clearInterval(this.pingIntervalID);
145 | }
146 |
147 | this.listeners.forEach(function (listener) {
148 | listener.destroy();
149 | });
150 |
151 | this.peer.close();
152 | };
153 |
154 | /* Event bindings
155 | –––––––––––––––––––––––––––––––––––––––––––––––––– */
156 |
157 | ProxyControlsClient.prototype.onConnect = function () {
158 | console.info('connect()');
159 | this.listeners.forEach(function (listener) { listener.bind(); });
160 | this.resetPings();
161 |
162 | };
163 |
164 | ProxyControlsClient.prototype.onConnectError = function () {
165 | console.error('connect_error()');
166 | };
167 |
168 | ProxyControlsClient.prototype.onConnectTimeout = function () {
169 | console.warn('connect_timeout()');
170 | };
171 |
172 | ProxyControlsClient.prototype.onDisconnect = function () {
173 | console.info('disconnect()');
174 | this.listeners.forEach(function (listener) { listener.unbind(); });
175 | this.resetPings();
176 | };
177 |
178 | ProxyControlsClient.prototype.onData = function (event) {
179 | if (event.type === 'ping') {
180 | var protocol = this.getPeerProtocol();
181 | if (!protocol) return;
182 | this.pingList[this.pingListIndex] = (Date.now() - event.timestamp) / 2;
183 | this.pingListIndex = (this.pingListIndex + 1) % POLLING[protocol].BUFFER_SIZE;
184 | } else {
185 | console.log('data(%s)', JSON.stringify(event, null, 2));
186 | }
187 | };
188 |
189 | ProxyControlsClient.prototype.onError = function () {
190 | console.error('error()');
191 | };
192 |
193 | ProxyControlsClient.prototype.onClose = function () {
194 | console.info('close()');
195 | this.listeners.forEach(function (listener) { listener.unbind(); });
196 | this.resetPings();
197 | };
198 |
199 | ProxyControlsClient.prototype.onUpgrade = function () {
200 | console.info('upgrade()');
201 | this.resetPings();
202 | };
203 |
204 | ProxyControlsClient.prototype.onDowngrade = function () {
205 | console.info('downgrade()');
206 | this.resetPings();
207 | };
208 |
209 |
210 | /* Accessors
211 | –––––––––––––––––––––––––––––––––––––––––––––––––– */
212 |
213 | ProxyControlsClient.prototype.isServerConnected = function () {
214 | return this.peer.socket.readyState === WebSocket.OPEN;
215 | };
216 |
217 | ProxyControlsClient.prototype.isPeerConnected = function () {
218 | return this.peer.peerConnected;
219 | };
220 |
221 | ProxyControlsClient.prototype.getPeerProtocol = function () {
222 | if (this.peer.rtcConnected) {
223 | return 'rtc';
224 | } else if (this.peer.socketConnected && this.peer.peerConnected) {
225 | return 'socket';
226 | }
227 | return null;
228 | };
229 |
230 | ProxyControlsClient.prototype.getPeerLatency = function () {
231 | if (!this.pingList.length) return NaN;
232 | var avgLatency = 0;
233 | for (var i = 0; i < this.pingList.length; i++) {
234 | avgLatency += this.pingList[i];
235 | }
236 | return Math.round(avgLatency / this.pingList.length);
237 | };
238 |
239 | module.exports = ProxyControlsClient;
240 |
--------------------------------------------------------------------------------
/client/assets/app.css:
--------------------------------------------------------------------------------
1 | /* Client CSS
2 | –––––––––––––––––––––––––––––––––––––––––––––––––– */
3 |
4 | /* Page layout
5 | –––––––––––––––––––––––––––––––––––––––––––––––––– */
6 |
7 | html {
8 | background: #222;
9 | }
10 |
11 | body {
12 | background: #FFF;
13 | min-height: 100vh;
14 | overflow-y: scroll;
15 | box-sizing: border-box;
16 | padding-top: 60px;
17 | }
18 |
19 | .container {
20 | max-width: 850px;
21 | }
22 |
23 | .view {
24 | padding-top: 2rem;
25 | }
26 |
27 | section {
28 | margin-bottom: 6rem;
29 | }
30 |
31 | @media screen and (min-width: 750px) {
32 | .view { padding-top: 4rem; }
33 | }
34 |
35 | /* Typography
36 | –––––––––––––––––––––––––––––––––––––––––––––––––– */
37 |
38 | h1 { font-size: 5.0rem; }
39 | h2 { font-size: 3.6rem; }
40 | h3 { font-size: 3.0rem; }
41 | h4 { font-size: 2.4rem; }
42 | body { font-size: 1.8rem; }
43 |
44 | p { font-weight: 300; line-height: 1.7em; }
45 |
46 | a { text-decoration: none; }
47 | a:hover { text-decoration: underline; }
48 |
49 | .caps { font-size: 1.5rem; font-weight: 400; }
50 | .a-disabled { color: #888; }
51 | .a-icon { height: 2rem; vertical-align: text-top; }
52 | .a-with-icon { white-space: nowrap; }
53 |
54 | /* Flexbox
55 | –––––––––––––––––––––––––––––––––––––––––––––––––– */
56 |
57 | .flex {
58 | display: -webkit-flex;
59 | display: -moz-flex;
60 | display: -ms-flex;
61 | display: flex;
62 | }
63 |
64 | .flex-column {
65 | -webkit-flex-direction: column;
66 | -moz-flex-direction: column;
67 | -ms-flex-direction: column;
68 | flex-direction: column;
69 | }
70 |
71 | .flex-grow {
72 | -webkit-flex-grow: 1;
73 | -moz-flex-grow: 1;
74 | -ms-flex-grow: 1;
75 | flex-grow: 1;
76 | }
77 |
78 | .flex-center {
79 | -webkit-justify-content: center;
80 | -moz-justify-content: center;
81 | -ms-justify-content: center;
82 | justify-content: center;
83 | }
84 |
85 | .flex-space-around {
86 | -webkit-justify-content: space-around;
87 | -moz-justify-content: space-around;
88 | -ms-justify-content: space-around;
89 | justify-content: space-around;
90 | }
91 |
92 | /* Header
93 | –––––––––––––––––––––––––––––––––––––––––––––––––– */
94 |
95 | header {
96 | position: fixed;
97 |
98 | top: 0;
99 | width: 100%;
100 | height: 60px;
101 | line-height: 60px;
102 | background: #222;
103 | z-index: 1000;
104 | }
105 |
106 | header h4,
107 | header nav {
108 | margin: 0;
109 | line-height: inherit;
110 | display: inline-block;
111 | }
112 |
113 | header > .container {
114 | position: static;
115 | }
116 |
117 | .logo,
118 | .logo:hover {
119 | color: #FFF;
120 | text-decoration: none;
121 | }
122 |
123 | .version {
124 | font-size: 0.4em;
125 | font-weight: 600;
126 | text-transform: uppercase;
127 | position: relative;
128 | bottom: 1em;
129 | color: #1EAEDB;
130 | }
131 |
132 | /* Navigation
133 | –––––––––––––––––––––––––––––––––––––––––––––––––– */
134 |
135 | .menu-btn {
136 | position: absolute;
137 | right: 0;
138 | top: 0;
139 | height: 60px;
140 | padding: 0 30px;
141 | }
142 |
143 | .menu-btn:active { background: #444; }
144 |
145 | .menu-btn:before {
146 | content: "";
147 | position: absolute;
148 | left: 15px;
149 | top: 22px;
150 | width: 30px;
151 | height: 0.12em;
152 | background: #F0F0F0;
153 | box-shadow:
154 | 0 0.35em 0 0 #F0F0F0,
155 | 0 0.7em 0 0 #F0F0F0;
156 | }
157 |
158 | nav {
159 | position: absolute;
160 | top: 60px;
161 | right: -250px;
162 | width: 250px;
163 | height: calc(100vh - 60px);
164 |
165 | -webkit-transition: right 0.2s ease;
166 | -moz-transition: right 0.2s ease;
167 | -ms-transition: right 0.2s ease;
168 | transition: right 0.2s ease;
169 |
170 | -webkit-backdrop-filter: blur(5px);
171 | -moz-backdrop-filter: blur(5px);
172 | -ms-backdrop-filter: blur(5px);
173 | backdrop-filter: blur(5px);
174 | background: rgba(40,40,40,0.95);
175 | }
176 |
177 | nav.open { right: 0; }
178 |
179 | .nav-link {
180 | display: block;
181 | height: 60px;
182 | box-sizing: border-box;
183 | border-left: 5px solid transparent;
184 | padding: 0 1em;
185 | font-size: 1.8rem;
186 | font-weight: 400;
187 | color: #FFF;
188 | text-decoration: none;
189 | }
190 |
191 | .nav-link.active { border-left: 5px solid #33C3F0; }
192 |
193 | .nav-link:hover,
194 | .nav-link:active {
195 | color: #FFF;
196 | text-decoration: none;
197 |
198 | -webkit-backdrop-filter: blur(5px);
199 | -moz-backdrop-filter: blur(5px);
200 | -ms-backdrop-filter: blur(5px);
201 | backdrop-filter: blur(5px);
202 | background: rgba(34, 34, 34, 0.5);
203 | }
204 |
205 | @media screen and (min-width: 750px) {
206 | .menu-btn { display: none; }
207 |
208 | nav {
209 | position: static;
210 | width: auto;
211 | height: 60px;
212 | background: none;
213 | }
214 |
215 | .nav-link {
216 | display: inline-block;
217 | float: left;
218 | border-left: 0;
219 | }
220 |
221 | .nav-link.active {
222 | border-left: 0;
223 | border-bottom: 5px solid #33C3F0;
224 | }
225 |
226 | .nav-link:hover,
227 | .nav-link:active {
228 | -webkit-backdrop-filter: none;
229 | -moz-backdrop-filter: none;
230 | -ms-backdrop-filter: none;
231 | backdrop-filter: none;
232 | background: #444;
233 | }
234 |
235 | }
236 |
237 | /* Footer
238 | –––––––––––––––––––––––––––––––––––––––––––––––––– */
239 |
240 | footer {
241 | padding: 3rem 0;
242 | font-size: 1.5rem;
243 | background: #222222;
244 | color: #bfbfbf;
245 | }
246 |
247 | .footer-links {
248 | margin-bottom: 1em;
249 | text-align: center;
250 | }
251 |
252 | .footer-link {
253 | display: block;
254 | line-height: 2em;
255 | }
256 |
257 | .footer-desc p:last-child { margin-bottom: 0; }
258 |
259 | footer a.attrib {
260 | color: inherit;
261 | }
262 |
263 | @media screen and (min-width: 550px) {
264 | .footer-links {
265 | margin-top: 0.2em;
266 | margin-bottom: 0;
267 | text-align: left;
268 | }
269 |
270 | .footer-link {
271 | line-height: 1.2em;
272 | }
273 | }
274 |
275 | /* Home
276 | –––––––––––––––––––––––––––––––––––––––––––––––––– */
277 |
278 | body.bg-dark { background: url("footer_lodyas.png"); }
279 |
280 | .home-view {
281 | color: #FFF;
282 | padding: 2rem;
283 | text-align: center;
284 | }
285 |
286 | .home-view > .container {
287 | padding: 0;
288 | }
289 |
290 | .home-title {
291 | font-size: 3.6rem;
292 | font-weight: 300;
293 | text-align: center;
294 | text-shadow: 1px 1px 1px #000;
295 | }
296 |
297 | .home-subtitle {
298 | font-size: 2.2rem;
299 | max-width: 600px;
300 | margin: 0 auto 2rem auto;
301 | }
302 |
303 | .home-caption {
304 | margin-top: 1.5rem;
305 | text-align: left;
306 | }
307 |
308 | .home-view .button {
309 | color: #FFF;
310 | }
311 |
312 | @media screen and (min-width: 550px) {
313 | .home-title { font-size: 5rem; }
314 | .home-subtitle { font-size: 3rem; }
315 | .home-caption { text-align: center; }
316 | }
317 |
318 | /* Getting Started
319 | –––––––––––––––––––––––––––––––––––––––––––––––––– */
320 |
321 | /* Device notice */
322 |
323 | .device-notice {
324 | border-left: 5px solid #DAA520;
325 | padding-left: 1em;
326 | }
327 |
328 | i.alert {
329 | display: inline-block;
330 | position: relative;
331 | top: 0.1em;
332 | margin-right: 0.2em;
333 | margin-top: -0.5em;
334 |
335 | border: 0.6em solid transparent;
336 | border-bottom: 1em solid #DAA520;
337 | }
338 |
339 | i.alert:after {
340 | position: absolute;
341 | left: -0.1em;
342 | top: 0.1em;
343 |
344 | font-style: normal;
345 | font-size: 0.8em;
346 | content: "!";
347 | color: #FFF;
348 | }
349 |
350 | @media screen and (min-width: 550px) {
351 | .device-notice { display: none; }
352 | }
353 |
354 | /* Pair code input */
355 |
356 | input.ng-invalid,
357 | input.ng-invalid:focus { border-color: #ED143D; }
358 |
359 | /* Connection status */
360 |
361 | .status {
362 | color: #FFF;
363 | border-radius: 4px;
364 | padding: 3rem;
365 | margin-bottom: 1.5rem;
366 | }
367 |
368 | .status.status-pending { background: #DAA520; }
369 | .status.status-connected { background: #33C3F0; }
370 |
371 |
372 | .status .button {
373 | color: #fff;
374 | background: rgba(255,255,255,0.2);
375 | border: 0;
376 | margin: 0;
377 | }
378 |
379 | .status .button:hover { background: #ED143D; }
380 |
381 | /* Modules
382 | –––––––––––––––––––––––––––––––––––––––––––––––––– */
383 |
384 | .module {
385 | background: #F8F8F8;
386 | border-radius: 4px;
387 | padding: 3rem;
388 | }
389 |
390 | .module + .module { margin-top: 1rem; }
391 | .module:last-child { margin-bottom: 2rem; }
392 |
393 | .module ul,
394 | .module li:last-child {
395 | margin-bottom: 0;
396 | }
397 |
398 | .module .button {
399 | height: 30px;
400 | width: 95px;
401 | line-height: 30px;
402 | padding-left: 20px;
403 | padding-right: 20px;
404 | }
405 |
406 | /* Toggles
407 | –––––––––––––––––––––––––––––––––––––––––––––––––– */
408 |
409 | .toggle {
410 | color: #FFF;
411 | padding: 0.3rem 0.5rem;
412 | border-radius: 0.2rem;
413 | }
414 |
415 | .toggle.green { background: #008000; }
416 | .toggle.yellow { background: #DAA520; }
417 | .toggle.red { background: #ED143D; }
418 |
419 | /* Docs
420 | –––––––––––––––––––––––––––––––––––––––––––––––––– */
421 |
422 | .docs-view hr {
423 | margin: 6rem 0;
424 | }
425 |
--------------------------------------------------------------------------------
/client/assets/normalize.css:
--------------------------------------------------------------------------------
1 | /*! normalize.css v3.0.2 | MIT License | git.io/normalize */
2 |
3 | /**
4 | * 1. Set default font family to sans-serif.
5 | * 2. Prevent iOS text size adjust after orientation change, without disabling
6 | * user zoom.
7 | */
8 |
9 | html {
10 | font-family: sans-serif; /* 1 */
11 | -ms-text-size-adjust: 100%; /* 2 */
12 | -webkit-text-size-adjust: 100%; /* 2 */
13 | }
14 |
15 | /**
16 | * Remove default margin.
17 | */
18 |
19 | body {
20 | margin: 0;
21 | }
22 |
23 | /* HTML5 display definitions
24 | ========================================================================== */
25 |
26 | /**
27 | * Correct `block` display not defined for any HTML5 element in IE 8/9.
28 | * Correct `block` display not defined for `details` or `summary` in IE 10/11
29 | * and Firefox.
30 | * Correct `block` display not defined for `main` in IE 11.
31 | */
32 |
33 | article,
34 | aside,
35 | details,
36 | figcaption,
37 | figure,
38 | footer,
39 | header,
40 | hgroup,
41 | main,
42 | menu,
43 | nav,
44 | section,
45 | summary {
46 | display: block;
47 | }
48 |
49 | /**
50 | * 1. Correct `inline-block` display not defined in IE 8/9.
51 | * 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera.
52 | */
53 |
54 | audio,
55 | canvas,
56 | progress,
57 | video {
58 | display: inline-block; /* 1 */
59 | vertical-align: baseline; /* 2 */
60 | }
61 |
62 | /**
63 | * Prevent modern browsers from displaying `audio` without controls.
64 | * Remove excess height in iOS 5 devices.
65 | */
66 |
67 | audio:not([controls]) {
68 | display: none;
69 | height: 0;
70 | }
71 |
72 | /**
73 | * Address `[hidden]` styling not present in IE 8/9/10.
74 | * Hide the `template` element in IE 8/9/11, Safari, and Firefox < 22.
75 | */
76 |
77 | [hidden],
78 | template {
79 | display: none;
80 | }
81 |
82 | /* Links
83 | ========================================================================== */
84 |
85 | /**
86 | * Remove the gray background color from active links in IE 10.
87 | */
88 |
89 | a {
90 | background-color: transparent;
91 | }
92 |
93 | /**
94 | * Improve readability when focused and also mouse hovered in all browsers.
95 | */
96 |
97 | a:active,
98 | a:hover {
99 | outline: 0;
100 | }
101 |
102 | /* Text-level semantics
103 | ========================================================================== */
104 |
105 | /**
106 | * Address styling not present in IE 8/9/10/11, Safari, and Chrome.
107 | */
108 |
109 | abbr[title] {
110 | border-bottom: 1px dotted;
111 | }
112 |
113 | /**
114 | * Address style set to `bolder` in Firefox 4+, Safari, and Chrome.
115 | */
116 |
117 | b,
118 | strong {
119 | font-weight: bold;
120 | }
121 |
122 | /**
123 | * Address styling not present in Safari and Chrome.
124 | */
125 |
126 | dfn {
127 | font-style: italic;
128 | }
129 |
130 | /**
131 | * Address variable `h1` font-size and margin within `section` and `article`
132 | * contexts in Firefox 4+, Safari, and Chrome.
133 | */
134 |
135 | h1 {
136 | font-size: 2em;
137 | margin: 0.67em 0;
138 | }
139 |
140 | /**
141 | * Address styling not present in IE 8/9.
142 | */
143 |
144 | mark {
145 | background: #ff0;
146 | color: #000;
147 | }
148 |
149 | /**
150 | * Address inconsistent and variable font size in all browsers.
151 | */
152 |
153 | small {
154 | font-size: 80%;
155 | }
156 |
157 | /**
158 | * Prevent `sub` and `sup` affecting `line-height` in all browsers.
159 | */
160 |
161 | sub,
162 | sup {
163 | font-size: 75%;
164 | line-height: 0;
165 | position: relative;
166 | vertical-align: baseline;
167 | }
168 |
169 | sup {
170 | top: -0.5em;
171 | }
172 |
173 | sub {
174 | bottom: -0.25em;
175 | }
176 |
177 | /* Embedded content
178 | ========================================================================== */
179 |
180 | /**
181 | * Remove border when inside `a` element in IE 8/9/10.
182 | */
183 |
184 | img {
185 | border: 0;
186 | }
187 |
188 | /**
189 | * Correct overflow not hidden in IE 9/10/11.
190 | */
191 |
192 | svg:not(:root) {
193 | overflow: hidden;
194 | }
195 |
196 | /* Grouping content
197 | ========================================================================== */
198 |
199 | /**
200 | * Address margin not present in IE 8/9 and Safari.
201 | */
202 |
203 | figure {
204 | margin: 1em 40px;
205 | }
206 |
207 | /**
208 | * Address differences between Firefox and other browsers.
209 | */
210 |
211 | hr {
212 | -moz-box-sizing: content-box;
213 | box-sizing: content-box;
214 | height: 0;
215 | }
216 |
217 | /**
218 | * Contain overflow in all browsers.
219 | */
220 |
221 | pre {
222 | overflow: auto;
223 | }
224 |
225 | /**
226 | * Address odd `em`-unit font size rendering in all browsers.
227 | */
228 |
229 | code,
230 | kbd,
231 | pre,
232 | samp {
233 | font-family: monospace, monospace;
234 | font-size: 1em;
235 | }
236 |
237 | /* Forms
238 | ========================================================================== */
239 |
240 | /**
241 | * Known limitation: by default, Chrome and Safari on OS X allow very limited
242 | * styling of `select`, unless a `border` property is set.
243 | */
244 |
245 | /**
246 | * 1. Correct color not being inherited.
247 | * Known issue: affects color of disabled elements.
248 | * 2. Correct font properties not being inherited.
249 | * 3. Address margins set differently in Firefox 4+, Safari, and Chrome.
250 | */
251 |
252 | button,
253 | input,
254 | optgroup,
255 | select,
256 | textarea {
257 | color: inherit; /* 1 */
258 | font: inherit; /* 2 */
259 | margin: 0; /* 3 */
260 | }
261 |
262 | /**
263 | * Address `overflow` set to `hidden` in IE 8/9/10/11.
264 | */
265 |
266 | button {
267 | overflow: visible;
268 | }
269 |
270 | /**
271 | * Address inconsistent `text-transform` inheritance for `button` and `select`.
272 | * All other form control elements do not inherit `text-transform` values.
273 | * Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera.
274 | * Correct `select` style inheritance in Firefox.
275 | */
276 |
277 | button,
278 | select {
279 | text-transform: none;
280 | }
281 |
282 | /**
283 | * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`
284 | * and `video` controls.
285 | * 2. Correct inability to style clickable `input` types in iOS.
286 | * 3. Improve usability and consistency of cursor style between image-type
287 | * `input` and others.
288 | */
289 |
290 | button,
291 | html input[type="button"], /* 1 */
292 | input[type="reset"],
293 | input[type="submit"] {
294 | -webkit-appearance: button; /* 2 */
295 | cursor: pointer; /* 3 */
296 | }
297 |
298 | /**
299 | * Re-set default cursor for disabled elements.
300 | */
301 |
302 | button[disabled],
303 | html input[disabled] {
304 | cursor: default;
305 | }
306 |
307 | /**
308 | * Remove inner padding and border in Firefox 4+.
309 | */
310 |
311 | button::-moz-focus-inner,
312 | input::-moz-focus-inner {
313 | border: 0;
314 | padding: 0;
315 | }
316 |
317 | /**
318 | * Address Firefox 4+ setting `line-height` on `input` using `!important` in
319 | * the UA stylesheet.
320 | */
321 |
322 | input {
323 | line-height: normal;
324 | }
325 |
326 | /**
327 | * It's recommended that you don't attempt to style these elements.
328 | * Firefox's implementation doesn't respect box-sizing, padding, or width.
329 | *
330 | * 1. Address box sizing set to `content-box` in IE 8/9/10.
331 | * 2. Remove excess padding in IE 8/9/10.
332 | */
333 |
334 | input[type="checkbox"],
335 | input[type="radio"] {
336 | box-sizing: border-box; /* 1 */
337 | padding: 0; /* 2 */
338 | }
339 |
340 | /**
341 | * Fix the cursor style for Chrome's increment/decrement buttons. For certain
342 | * `font-size` values of the `input`, it causes the cursor style of the
343 | * decrement button to change from `default` to `text`.
344 | */
345 |
346 | input[type="number"]::-webkit-inner-spin-button,
347 | input[type="number"]::-webkit-outer-spin-button {
348 | height: auto;
349 | }
350 |
351 | /**
352 | * 1. Address `appearance` set to `searchfield` in Safari and Chrome.
353 | * 2. Address `box-sizing` set to `border-box` in Safari and Chrome
354 | * (include `-moz` to future-proof).
355 | */
356 |
357 | input[type="search"] {
358 | -webkit-appearance: textfield; /* 1 */
359 | -moz-box-sizing: content-box;
360 | -webkit-box-sizing: content-box; /* 2 */
361 | box-sizing: content-box;
362 | }
363 |
364 | /**
365 | * Remove inner padding and search cancel button in Safari and Chrome on OS X.
366 | * Safari (but not Chrome) clips the cancel button when the search input has
367 | * padding (and `textfield` appearance).
368 | */
369 |
370 | input[type="search"]::-webkit-search-cancel-button,
371 | input[type="search"]::-webkit-search-decoration {
372 | -webkit-appearance: none;
373 | }
374 |
375 | /**
376 | * Define consistent border, margin, and padding.
377 | */
378 |
379 | fieldset {
380 | border: 1px solid #c0c0c0;
381 | margin: 0 2px;
382 | padding: 0.35em 0.625em 0.75em;
383 | }
384 |
385 | /**
386 | * 1. Correct `color` not being inherited in IE 8/9/10/11.
387 | * 2. Remove padding so people aren't caught out if they zero out fieldsets.
388 | */
389 |
390 | legend {
391 | border: 0; /* 1 */
392 | padding: 0; /* 2 */
393 | }
394 |
395 | /**
396 | * Remove default vertical scrollbar in IE 8/9/10/11.
397 | */
398 |
399 | textarea {
400 | overflow: auto;
401 | }
402 |
403 | /**
404 | * Don't inherit the `font-weight` (applied by a rule above).
405 | * NOTE: the default cannot safely be changed in Chrome and Safari on OS X.
406 | */
407 |
408 | optgroup {
409 | font-weight: bold;
410 | }
411 |
412 | /* Tables
413 | ========================================================================== */
414 |
415 | /**
416 | * Remove most spacing between table cells.
417 | */
418 |
419 | table {
420 | border-collapse: collapse;
421 | border-spacing: 0;
422 | }
423 |
424 | td,
425 | th {
426 | padding: 0;
427 | }
--------------------------------------------------------------------------------
/client/assets/skeleton.css:
--------------------------------------------------------------------------------
1 | /*
2 | * Skeleton V2.0.4
3 | * Copyright 2014, Dave Gamache
4 | * www.getskeleton.com
5 | * Free to use under the MIT license.
6 | * http://www.opensource.org/licenses/mit-license.php
7 | * 12/29/2014
8 | */
9 |
10 |
11 | /* Table of contents
12 | ––––––––––––––––––––––––––––––––––––––––––––––––––
13 | - Grid
14 | - Base Styles
15 | - Typography
16 | - Links
17 | - Buttons
18 | - Forms
19 | - Lists
20 | - Code
21 | - Tables
22 | - Spacing
23 | - Utilities
24 | - Clearing
25 | - Media Queries
26 | */
27 |
28 |
29 | /* Grid
30 | –––––––––––––––––––––––––––––––––––––––––––––––––– */
31 | .container {
32 | position: relative;
33 | width: 100%;
34 | max-width: 960px;
35 | margin: 0 auto;
36 | padding: 0 20px;
37 | box-sizing: border-box; }
38 | .column,
39 | .columns {
40 | width: 100%;
41 | float: left;
42 | box-sizing: border-box; }
43 |
44 | /* For devices larger than 400px */
45 | @media (min-width: 400px) {
46 | .container {
47 | width: 85%;
48 | padding: 0; }
49 | }
50 |
51 | /* For devices larger than 550px */
52 | @media (min-width: 550px) {
53 | .container {
54 | width: 80%; }
55 | .column,
56 | .columns {
57 | margin-left: 4%; }
58 | .column:first-child,
59 | .columns:first-child {
60 | margin-left: 0; }
61 |
62 | .one.column,
63 | .one.columns { width: 4.66666666667%; }
64 | .two.columns { width: 13.3333333333%; }
65 | .three.columns { width: 22%; }
66 | .four.columns { width: 30.6666666667%; }
67 | .five.columns { width: 39.3333333333%; }
68 | .six.columns { width: 48%; }
69 | .seven.columns { width: 56.6666666667%; }
70 | .eight.columns { width: 65.3333333333%; }
71 | .nine.columns { width: 74.0%; }
72 | .ten.columns { width: 82.6666666667%; }
73 | .eleven.columns { width: 91.3333333333%; }
74 | .twelve.columns { width: 100%; margin-left: 0; }
75 |
76 | .one-third.column { width: 30.6666666667%; }
77 | .two-thirds.column { width: 65.3333333333%; }
78 |
79 | .one-half.column { width: 48%; }
80 |
81 | /* Offsets */
82 | .offset-by-one.column,
83 | .offset-by-one.columns { margin-left: 8.66666666667%; }
84 | .offset-by-two.column,
85 | .offset-by-two.columns { margin-left: 17.3333333333%; }
86 | .offset-by-three.column,
87 | .offset-by-three.columns { margin-left: 26%; }
88 | .offset-by-four.column,
89 | .offset-by-four.columns { margin-left: 34.6666666667%; }
90 | .offset-by-five.column,
91 | .offset-by-five.columns { margin-left: 43.3333333333%; }
92 | .offset-by-six.column,
93 | .offset-by-six.columns { margin-left: 52%; }
94 | .offset-by-seven.column,
95 | .offset-by-seven.columns { margin-left: 60.6666666667%; }
96 | .offset-by-eight.column,
97 | .offset-by-eight.columns { margin-left: 69.3333333333%; }
98 | .offset-by-nine.column,
99 | .offset-by-nine.columns { margin-left: 78.0%; }
100 | .offset-by-ten.column,
101 | .offset-by-ten.columns { margin-left: 86.6666666667%; }
102 | .offset-by-eleven.column,
103 | .offset-by-eleven.columns { margin-left: 95.3333333333%; }
104 |
105 | .offset-by-one-third.column,
106 | .offset-by-one-third.columns { margin-left: 34.6666666667%; }
107 | .offset-by-two-thirds.column,
108 | .offset-by-two-thirds.columns { margin-left: 69.3333333333%; }
109 |
110 | .offset-by-one-half.column,
111 | .offset-by-one-half.columns { margin-left: 52%; }
112 |
113 | }
114 |
115 |
116 | /* Base Styles
117 | –––––––––––––––––––––––––––––––––––––––––––––––––– */
118 | /* NOTE
119 | html is set to 62.5% so that all the REM measurements throughout Skeleton
120 | are based on 10px sizing. So basically 1.5rem = 15px :) */
121 | html {
122 | font-size: 62.5%; }
123 | body {
124 | font-size: 1.5em; /* currently ems cause chrome bug misinterpreting rems on body element */
125 | line-height: 1.6;
126 | font-weight: 400;
127 | font-family: "Raleway", "HelveticaNeue", "Helvetica Neue", Helvetica, Arial, sans-serif;
128 | color: #222; }
129 |
130 |
131 | /* Typography
132 | –––––––––––––––––––––––––––––––––––––––––––––––––– */
133 | h1, h2, h3, h4, h5, h6 {
134 | margin-top: 0;
135 | margin-bottom: 2rem;
136 | font-weight: 300; }
137 | h1 { font-size: 4.0rem; line-height: 1.2; letter-spacing: -.1rem;}
138 | h2 { font-size: 3.6rem; line-height: 1.25; letter-spacing: -.1rem; }
139 | h3 { font-size: 3.0rem; line-height: 1.3; letter-spacing: -.1rem; }
140 | h4 { font-size: 2.4rem; line-height: 1.35; letter-spacing: -.08rem; }
141 | h5 { font-size: 1.8rem; line-height: 1.5; letter-spacing: -.05rem; }
142 | h6 { font-size: 1.5rem; line-height: 1.6; letter-spacing: 0; }
143 |
144 | /* Larger than phablet */
145 | @media (min-width: 550px) {
146 | h1 { font-size: 5.0rem; }
147 | h2 { font-size: 4.2rem; }
148 | h3 { font-size: 3.6rem; }
149 | h4 { font-size: 3.0rem; }
150 | h5 { font-size: 2.4rem; }
151 | h6 { font-size: 1.5rem; }
152 | }
153 |
154 | p {
155 | margin-top: 0; }
156 |
157 |
158 | /* Links
159 | –––––––––––––––––––––––––––––––––––––––––––––––––– */
160 | a {
161 | color: #1EAEDB; }
162 | a:hover {
163 | color: #0FA0CE; }
164 |
165 |
166 | /* Buttons
167 | –––––––––––––––––––––––––––––––––––––––––––––––––– */
168 | .button,
169 | button,
170 | input[type="submit"],
171 | input[type="reset"],
172 | input[type="button"] {
173 | display: inline-block;
174 | height: 38px;
175 | padding: 0 30px;
176 | color: #555;
177 | text-align: center;
178 | font-size: 11px;
179 | font-weight: 600;
180 | line-height: 38px;
181 | letter-spacing: .1rem;
182 | text-transform: uppercase;
183 | text-decoration: none;
184 | white-space: nowrap;
185 | background-color: transparent;
186 | border-radius: 4px;
187 | border: 1px solid #bbb;
188 | cursor: pointer;
189 | box-sizing: border-box; }
190 | .button:hover,
191 | button:hover,
192 | input[type="submit"]:hover,
193 | input[type="reset"]:hover,
194 | input[type="button"]:hover,
195 | .button:focus,
196 | button:focus,
197 | input[type="submit"]:focus,
198 | input[type="reset"]:focus,
199 | input[type="button"]:focus {
200 | color: #333;
201 | border-color: #888;
202 | outline: 0; }
203 | .button.button-primary,
204 | button.button-primary,
205 | input[type="submit"].button-primary,
206 | input[type="reset"].button-primary,
207 | input[type="button"].button-primary {
208 | color: #FFF;
209 | background-color: #33C3F0;
210 | border-color: #33C3F0; }
211 | .button.button-primary:hover,
212 | button.button-primary:hover,
213 | input[type="submit"].button-primary:hover,
214 | input[type="reset"].button-primary:hover,
215 | input[type="button"].button-primary:hover,
216 | .button.button-primary:focus,
217 | button.button-primary:focus,
218 | input[type="submit"].button-primary:focus,
219 | input[type="reset"].button-primary:focus,
220 | input[type="button"].button-primary:focus {
221 | color: #FFF;
222 | background-color: #1EAEDB;
223 | border-color: #1EAEDB; }
224 |
225 |
226 | /* Forms
227 | –––––––––––––––––––––––––––––––––––––––––––––––––– */
228 | input[type="email"],
229 | input[type="number"],
230 | input[type="search"],
231 | input[type="text"],
232 | input[type="tel"],
233 | input[type="url"],
234 | input[type="password"],
235 | textarea,
236 | select {
237 | height: 38px;
238 | padding: 6px 10px; /* The 6px vertically centers text on FF, ignored by Webkit */
239 | background-color: #fff;
240 | border: 1px solid #D1D1D1;
241 | border-radius: 4px;
242 | box-shadow: none;
243 | box-sizing: border-box; }
244 | /* Removes awkward default styles on some inputs for iOS */
245 | input[type="email"],
246 | input[type="number"],
247 | input[type="search"],
248 | input[type="text"],
249 | input[type="tel"],
250 | input[type="url"],
251 | input[type="password"],
252 | textarea {
253 | -webkit-appearance: none;
254 | -moz-appearance: none;
255 | appearance: none; }
256 | textarea {
257 | min-height: 65px;
258 | padding-top: 6px;
259 | padding-bottom: 6px; }
260 | input[type="email"]:focus,
261 | input[type="number"]:focus,
262 | input[type="search"]:focus,
263 | input[type="text"]:focus,
264 | input[type="tel"]:focus,
265 | input[type="url"]:focus,
266 | input[type="password"]:focus,
267 | textarea:focus,
268 | select:focus {
269 | border: 1px solid #33C3F0;
270 | outline: 0; }
271 | label,
272 | legend {
273 | display: block;
274 | margin-bottom: .5rem;
275 | font-weight: 600; }
276 | fieldset {
277 | padding: 0;
278 | border-width: 0; }
279 | input[type="checkbox"],
280 | input[type="radio"] {
281 | display: inline; }
282 | label > .label-body {
283 | display: inline-block;
284 | margin-left: .5rem;
285 | font-weight: normal; }
286 |
287 |
288 | /* Lists
289 | –––––––––––––––––––––––––––––––––––––––––––––––––– */
290 | ul {
291 | list-style: circle inside; }
292 | ol {
293 | list-style: decimal inside; }
294 | ol, ul {
295 | padding-left: 0;
296 | margin-top: 0; }
297 | ul ul,
298 | ul ol,
299 | ol ol,
300 | ol ul {
301 | margin: 1.5rem 0 1.5rem 3rem;
302 | font-size: 90%; }
303 | li {
304 | margin-bottom: 1rem; }
305 |
306 |
307 | /* Code
308 | –––––––––––––––––––––––––––––––––––––––––––––––––– */
309 | code {
310 | padding: .2rem .5rem;
311 | margin: 0 .2rem;
312 | font-size: 90%;
313 | white-space: nowrap;
314 | background: #F1F1F1;
315 | border: 1px solid #E1E1E1;
316 | border-radius: 4px; }
317 | pre > code {
318 | display: block;
319 | padding: 1rem 1.5rem;
320 | white-space: pre; }
321 |
322 |
323 | /* Tables
324 | –––––––––––––––––––––––––––––––––––––––––––––––––– */
325 | th,
326 | td {
327 | padding: 12px 15px;
328 | text-align: left;
329 | border-bottom: 1px solid #E1E1E1; }
330 | th:first-child,
331 | td:first-child {
332 | padding-left: 0; }
333 | th:last-child,
334 | td:last-child {
335 | padding-right: 0; }
336 |
337 |
338 | /* Spacing
339 | –––––––––––––––––––––––––––––––––––––––––––––––––– */
340 | button,
341 | .button {
342 | margin-bottom: 1rem; }
343 | input,
344 | textarea,
345 | select,
346 | fieldset {
347 | margin-bottom: 1.5rem; }
348 | pre,
349 | blockquote,
350 | dl,
351 | figure,
352 | table,
353 | p,
354 | ul,
355 | ol,
356 | form {
357 | margin-bottom: 2.5rem; }
358 |
359 |
360 | /* Utilities
361 | –––––––––––––––––––––––––––––––––––––––––––––––––– */
362 | .u-full-width {
363 | width: 100%;
364 | box-sizing: border-box; }
365 | .u-max-full-width {
366 | max-width: 100%;
367 | box-sizing: border-box; }
368 | .u-pull-right {
369 | float: right; }
370 | .u-pull-left {
371 | float: left; }
372 |
373 |
374 | /* Misc
375 | –––––––––––––––––––––––––––––––––––––––––––––––––– */
376 | hr {
377 | margin-top: 3rem;
378 | margin-bottom: 3.5rem;
379 | border-width: 0;
380 | border-top: 1px solid #E1E1E1; }
381 |
382 |
383 | /* Clearing
384 | –––––––––––––––––––––––––––––––––––––––––––––––––– */
385 |
386 | /* Self Clearing Goodness */
387 | .container:after,
388 | .row:after,
389 | .u-cf {
390 | content: "";
391 | display: table;
392 | clear: both; }
393 |
394 |
395 | /* Media Queries
396 | –––––––––––––––––––––––––––––––––––––––––––––––––– */
397 | /*
398 | Note: The best way to structure the use of media queries is to create the queries
399 | near the relevant code. For example, if you wanted to change the styles for buttons
400 | on small devices, paste the mobile query code up in the buttons section and style it
401 | there.
402 | */
403 |
404 |
405 | /* Larger than mobile */
406 | @media (min-width: 400px) {}
407 |
408 | /* Larger than phablet (also point when grid becomes active) */
409 | @media (min-width: 550px) {}
410 |
411 | /* Larger than tablet */
412 | @media (min-width: 750px) {}
413 |
414 | /* Larger than desktop */
415 | @media (min-width: 1000px) {}
416 |
417 | /* Larger than Desktop HD */
418 | @media (min-width: 1200px) {}
419 |
--------------------------------------------------------------------------------
/client/demo/assets/tree1.dae:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CINEMA4D 15.064 COLLADA Exporter
6 |
7 | 2015-08-28T05:40:04Z
8 | 2015-08-28T05:40:04Z
9 |
10 | Y_UP
11 |
12 |
13 |
14 | tex/shadow-circle.png
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | 0.8 0.8 0.8 1
24 |
25 |
26 | 0.2 0.2 0.2 1
27 |
28 |
29 | 0.5
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 | 0.0627451 0.396078 0.513725 1
41 |
42 |
43 | 0.2 0.2 0.2 1
44 |
45 |
46 | 0.5
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 | 0.0235294 0.564706 0.607843 1
58 |
59 |
60 | 0.2 0.2 0.2 1
61 |
62 |
63 | 0.5
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 | 0.164706 0.72549 0.701961 1
75 |
76 |
77 | 0.2 0.2 0.2 1
78 |
79 |
80 | 0.5
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 | 0.654902 0.47451 0.415686 1
92 |
93 |
94 | 0.2 0.2 0.2 1
95 |
96 |
97 | 0.5
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 | ID13
108 |
109 |
110 |
111 |
112 | ID14
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 | 1 1 1 1
122 |
123 |
124 | 0.88
125 |
126 |
127 | 1
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 | -40 -80.5 40 40 -80.5 40 40 -80.5 -40 -40 -80.5 -40 -40 -77.5711 47.0711 40 -77.5711 47.0711 45 -77.5711 45 47.0711 -77.5711 40 47.0711 -77.5711 -40 45 -77.5711 -45 40 -77.5711 -47.0711 -40 -77.5711 -47.0711 -45 -77.5711 -45 -47.0711 -77.5711 -40 -47.0711 -77.5711 40 -45 -77.5711 45 -40 -70.5 50 40 -70.5 50 47.0711 -70.5 47.0711 50 -70.5 40 50 -70.5 -40 47.0711 -70.5 -47.0711 40 -70.5 -50 -40 -70.5 -50 -47.0711 -70.5 -47.0711 -50 -70.5 -40 -50 -70.5 40 -47.0711 -70.5 47.0711 -40 70.5 50 40 70.5 50 47.0711 70.5 47.0711 50 70.5 40 50 70.5 -40 47.0711 70.5 -47.0711 40 70.5 -50 -40 70.5 -50 -47.0711 70.5 -47.0711 -50 70.5 -40 -50 70.5 40 -47.0711 70.5 47.0711 -40 77.5711 47.0711 40 77.5711 47.0711 45 77.5711 45 47.0711 77.5711 40 47.0711 77.5711 -40 45 77.5711 -45 40 77.5711 -47.0711 -40 77.5711 -47.0711 -45 77.5711 -45 -47.0711 77.5711 -40 -47.0711 77.5711 40 -45 77.5711 45 -40 80.5 40 40 80.5 40 40 80.5 -40 -40 80.5 -40
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 | -0.0785214 -0.921027 0.381502 0.0785214 -0.921027 0.381502 0 -0.382683 0.92388 0 0 1 0 0.382683 0.92388 -0.0785214 0.921027 0.381502 0.0785214 0.921027 0.381502 0.156558 -0.912487 0.377964 0.357407 -0.357407 0.862856 0.382683 0 0.92388 0.357407 0.357407 0.862856 0.156558 0.912487 0.377964 0.381502 -0.921027 0.0785214 0.377964 -0.912487 0.156558 0.862856 -0.357407 0.357407 0.92388 0 0.382683 0.862856 0.357407 0.357407 0.377964 0.912487 0.156558 0.381502 0.921027 0.0785214 0.381502 -0.921027 -0.0785214 0.92388 -0.382683 -0 1 0 -0 0.92388 0.382683 -0 0.381502 0.921027 -0.0785214 0.377964 -0.912487 -0.156558 0.862856 -0.357407 -0.357407 0.92388 0 -0.382683 0.862856 0.357407 -0.357407 0.377964 0.912487 -0.156558 0.0785214 -0.921027 -0.381502 0.156558 -0.912487 -0.377964 0.357407 -0.357407 -0.862856 0.382683 0 -0.92388 0.357407 0.357407 -0.862856 0.156558 0.912487 -0.377964 0.0785214 0.921027 -0.381502 -0.0785214 -0.921027 -0.381502 0 -0.382683 -0.92388 0 0 -1 0 0.382683 -0.92388 -0.0785214 0.921027 -0.381502 -0.156558 -0.912487 -0.377964 -0.357407 -0.357407 -0.862856 -0.382683 0 -0.92388 -0.357407 0.357407 -0.862856 -0.156558 0.912487 -0.377964 -0.381502 -0.921027 -0.0785214 -0.377964 -0.912487 -0.156558 -0.862856 -0.357407 -0.357407 -0.92388 0 -0.382683 -0.862856 0.357407 -0.357407 -0.377964 0.912487 -0.156558 -0.381502 0.921027 -0.0785214 -0.381502 -0.921027 0.0785214 -0.92388 -0.382683 -0 -1 0 -0 -0.92388 0.382683 -0 -0.381502 0.921027 0.0785214 -0.377964 -0.912487 0.156558 -0.862856 -0.357407 0.357407 -0.92388 0 0.382683 -0.862856 0.357407 0.357407 -0.377964 0.912487 0.156558 -0.156558 -0.912487 0.377964 -0.357407 -0.357407 0.862856 -0.382683 0 0.92388 -0.357407 0.357407 0.862856 -0.156558 0.912487 0.377964 0 1 -0 0 -1 -0
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 | 0 0 0 0.0455526 0.835876 0.0455526 0.835876 0 0 0.0911051 0.835876 0.0911051 0 0.908895 0.835876 0.908895 0 0.954447 0.835876 0.954447 0 1 0.835876 1 0.876907 0 0.917938 0.0455526 0.917938 0.0911051 0.917938 0.908895 0.917938 0.954447 0.876907 1 0.958969 0 1 0.0455526 1 0.0911051 1 0.908895 1 0.954447 0.958969 1 1 1 1 0
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 | 4 4 4 4 4 3 4 4 4 3 3 4 4 4 3 4 4 4 4 4 3 4 4 4 3 3 4 4 4 3 4 4 4 4 4 3 4 4 4 3 3 4 4 4 3 4 4 4 4 4 3 4 4 4 3 3 4 4 4 3 4 4
194 | 1 1 3 5 1 2 4 0 1 0 0 0 5 2 2 17 2 5 16 2 4 4 2 1 17 3 5 29 3 7 28 3 6 16 3 4 29 4 7 41 4 9 40 4 8 28 4 6 41 6 9 53 6 11 52 5 10 40 5 8 6 7 13 5 1 2 1 1 12 6 8 13 18 8 14 17 8 5 5 8 2 18 9 14 30 9 15 29 9 7 17 9 5 30 10 15 42 10 16 41 10 9 29 10 7 42 11 16 53 6 17 41 6 9 7 12 19 6 13 13 1 12 18 7 14 19 19 14 20 18 14 14 6 14 13 19 15 20 31 15 21 30 15 15 18 15 14 31 16 21 43 16 22 42 16 16 30 16 15 43 18 22 53 18 23 42 17 16 2 19 3 8 19 2 7 12 1 1 12 0 8 20 2 20 20 5 19 20 4 7 20 1 20 21 5 32 21 7 31 21 6 19 21 4 32 22 7 44 22 9 43 22 8 31 22 6 44 23 9 54 23 11 53 18 10 43 18 8 9 24 13 8 19 2 2 19 12 9 25 13 21 25 14 20 25 5 8 25 2 21 26 14 33 26 15 32 26 7 20 26 5 33 27 15 45 27 16 44 27 9 32 27 7 45 28 16 54 23 17 44 23 9 10 29 19 9 30 13 2 29 18 10 31 19 22 31 20 21 31 14 9 31 13 22 32 20 34 32 21 33 32 15 21 32 14 34 33 21 46 33 22 45 33 16 33 33 15 46 35 22 54 35 23 45 34 16 3 36 3 11 36 2 10 29 1 2 29 0 11 37 2 23 37 5 22 37 4 10 37 1 23 38 5 35 38 7 34 38 6 22 38 4 35 39 7 47 39 9 46 39 8 34 39 6 47 40 9 55 40 11 54 35 10 46 35 8 12 41 13 11 36 2 3 36 12 12 42 13 24 42 14 23 42 5 11 42 2 24 43 14 36 43 15 35 43 7 23 43 5 36 44 15 48 44 16 47 44 9 35 44 7 48 45 16 55 40 17 47 40 9 13 46 19 12 47 13 3 46 18 13 48 19 25 48 20 24 48 14 12 48 13 25 49 20 37 49 21 36 49 15 24 49 14 37 50 21 49 50 22 48 50 16 36 50 15 49 52 22 55 52 23 48 51 16 0 53 3 14 53 2 13 46 1 3 46 0 14 54 2 26 54 5 25 54 4 13 54 1 26 55 5 38 55 7 37 55 6 25 55 4 38 56 7 50 56 9 49 56 8 37 56 6 50 57 9 52 57 11 55 52 10 49 52 8 15 58 13 14 53 2 0 53 12 15 59 13 27 59 14 26 59 5 14 59 2 27 60 14 39 60 15 38 60 7 26 60 5 39 61 15 51 61 16 50 61 9 38 61 7 51 62 16 52 57 17 50 57 9 4 0 19 15 63 13 0 0 18 4 64 19 16 64 20 27 64 14 15 64 13 16 65 20 28 65 21 39 65 15 27 65 14 28 66 21 40 66 22 51 66 16 39 66 15 40 5 22 52 5 23 51 67 16 53 68 25 54 68 24 55 68 10 52 68 0 3 69 0 2 69 25 1 69 24 0 69 10
195 |
196 |
197 |
198 |
199 |
200 |
201 | 0 -50 0 0 50 0 10 -50 -0 10 -50 -0 10 50 -0 10 50 -0 8.66025 -50 -5 8.66025 -50 -5 8.66025 50 -5 8.66025 50 -5 5 -50 -8.66025 5 -50 -8.66025 5 50 -8.66025 5 50 -8.66025 6.12323e-16 -50 -10 6.12323e-16 -50 -10 6.12323e-16 50 -10 6.12323e-16 50 -10 -5 -50 -8.66025 -5 -50 -8.66025 -5 50 -8.66025 -5 50 -8.66025 -8.66025 -50 -5 -8.66025 -50 -5 -8.66025 50 -5 -8.66025 50 -5 -10 -50 -1.22465e-15 -10 -50 -1.22465e-15 -10 50 -1.22465e-15 -10 50 -1.22465e-15 -8.66025 -50 5 -8.66025 -50 5 -8.66025 50 5 -8.66025 50 5 -5 -50 8.66025 -5 -50 8.66025 -5 50 8.66025 -5 50 8.66025 -1.83697e-15 -50 10 -1.83697e-15 -50 10 -1.83697e-15 50 10 -1.83697e-15 50 10 5 -50 8.66025 5 -50 8.66025 5 50 8.66025 5 50 8.66025 8.66025 -50 5 8.66025 -50 5 8.66025 50 5 8.66025 50 5
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 | 0 -1 -0 1 0 -0 0.866026 0 -0.5 0 1 -0 0.5 0 -0.866026 0 0 -1 -0.5 0 -0.866026 -0.866026 0 -0.5 -1 0 -0 -0.866026 0 0.5 -0.5 0 0.866026 0 0 1 0.5 0 0.866026 0.866026 0 0.5
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 | 0.5 0.5 0 0.5 0.0669873 0.75 0 0 0 1 0.0833333 1 0.0833333 0 1 0.5 0.933013 0.75 0.25 0.933013 0.166667 1 0.166667 0 0.75 0.933013 0.5 1 0.25 1 0.25 0 0.333333 1 0.333333 0 0.416667 1 0.416667 0 0.5 0 0.933013 0.25 0.583333 1 0.583333 0 0.0669873 0.25 0.75 0.0669873 0.666667 1 0.666667 0 0.25 0.0669873 0.75 1 0.75 0 0.833333 1 0.833333 0 0.916667 1 0.916667 0 1 1 1 0
222 |
223 |
224 |
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 |
236 | 3 4 3 3 4 3 3 4 3 3 4 3 3 4 3 3 4 3 3 4 3 3 4 3 3 4 3 3 4 3 3 4 3 3 4 3
237 | 6 0 2 2 0 1 0 0 0 7 2 6 8 2 5 4 1 4 3 1 3 9 3 8 1 3 0 5 3 7 10 0 9 6 0 2 0 0 0 11 4 11 12 4 10 8 2 5 7 2 6 13 3 12 1 3 0 9 3 8 14 0 13 10 0 9 0 0 0 15 5 15 16 5 14 12 4 10 11 4 11 17 3 13 1 3 0 13 3 12 18 0 12 14 0 13 0 0 0 19 6 17 20 6 16 16 5 14 15 5 15 21 3 9 1 3 0 17 3 13 22 0 8 18 0 12 0 0 0 23 7 19 24 7 18 20 6 16 19 6 17 25 3 2 1 3 0 21 3 9 26 0 7 22 0 8 0 0 0 27 8 20 28 8 13 24 7 18 23 7 19 29 3 1 1 3 0 25 3 2 30 0 21 26 0 7 0 0 0 31 9 23 32 9 22 28 8 13 27 8 20 33 3 24 1 3 0 29 3 1 34 0 25 30 0 21 0 0 0 35 10 27 36 10 26 32 9 22 31 9 23 37 3 28 1 3 0 33 3 24 38 0 20 34 0 25 0 0 0 39 11 30 40 11 29 36 10 26 35 10 27 41 3 20 1 3 0 37 3 28 42 0 28 38 0 20 0 0 0 43 12 32 44 12 31 40 11 29 39 11 30 45 3 25 1 3 0 41 3 20 46 0 24 42 0 28 0 0 0 47 13 34 48 13 33 44 12 31 43 12 32 49 3 21 1 3 0 45 3 25 2 0 1 46 0 24 0 0 0 3 1 36 4 1 35 48 13 33 47 13 34 5 3 7 1 3 0 49 3 21
238 |
239 |
240 |
241 |
242 |
243 |
244 | -50 0 50 50 0 50 -50 0 -50 50 0 -50
245 |
246 |
247 |
248 |
249 |
250 |
251 |
252 |
253 |
254 | 0 1 -0
255 |
256 |
257 |
258 |
259 |
260 |
261 |
262 |
263 |
264 | 0 0 0 1 1 1 1 0
265 |
266 |
267 |
268 |
269 |
270 |
271 |
272 |
273 |
274 |
275 |
276 |
277 |
278 |
279 | 4
280 | 1 0 3 3 0 2 2 0 1 0 0 0
281 |
282 |
283 |
284 |
285 |
286 |
287 |
288 | 0 0 -0
289 | 0 1 0 -0
290 | 1 0 0 0
291 | 0 0 1 -0
292 | 1 1 1
293 |
294 | 0 180 -0
295 | 0 1 0 -0
296 | 1 0 0 0
297 | 0 0 1 -0
298 | 1 1 1
299 |
300 |
301 |
302 |
303 |
304 |
305 |
306 |
307 |
308 |
309 |
310 | 0 50 -0
311 | 0 1 0 -0
312 | 1 0 0 0
313 | 0 0 1 -0
314 | 1 1 1
315 |
316 |
317 |
318 |
319 |
320 |
321 |
322 |
323 |
324 |
325 |
326 | 0 0 -0
327 | 0 1 0 -0
328 | 1 0 0 0
329 | 0 0 1 -0
330 | 1 1 1
331 |
332 |
333 |
334 |
335 |
336 |
337 |
338 |
339 |
340 |
341 |
342 |
343 |
344 |
345 |
346 |
347 |
348 |
--------------------------------------------------------------------------------
/client/lib/keyboard.polyfill.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Polyfill for the additional KeyboardEvent properties defined in the D3E and
3 | * D4E draft specifications, by @inexorabletash.
4 | *
5 | * See: https://github.com/inexorabletash/polyfill
6 | */
7 | (function(global) {
8 | var nativeKeyboardEvent = ('KeyboardEvent' in global);
9 | if (!nativeKeyboardEvent)
10 | global.KeyboardEvent = function KeyboardEvent() { throw TypeError('Illegal constructor'); };
11 |
12 | global.KeyboardEvent.DOM_KEY_LOCATION_STANDARD = 0x00; // Default or unknown location
13 | global.KeyboardEvent.DOM_KEY_LOCATION_LEFT = 0x01; // e.g. Left Alt key
14 | global.KeyboardEvent.DOM_KEY_LOCATION_RIGHT = 0x02; // e.g. Right Alt key
15 | global.KeyboardEvent.DOM_KEY_LOCATION_NUMPAD = 0x03; // e.g. Numpad 0 or +
16 |
17 | var STANDARD = window.KeyboardEvent.DOM_KEY_LOCATION_STANDARD,
18 | LEFT = window.KeyboardEvent.DOM_KEY_LOCATION_LEFT,
19 | RIGHT = window.KeyboardEvent.DOM_KEY_LOCATION_RIGHT,
20 | NUMPAD = window.KeyboardEvent.DOM_KEY_LOCATION_NUMPAD;
21 |
22 | //--------------------------------------------------------------------
23 | //
24 | // Utilities
25 | //
26 | //--------------------------------------------------------------------
27 |
28 | function contains(s, ss) { return String(s).indexOf(ss) !== -1; }
29 |
30 | var os = (function() {
31 | if (contains(navigator.platform, 'Win')) { return 'win'; }
32 | if (contains(navigator.platform, 'Mac')) { return 'mac'; }
33 | if (contains(navigator.platform, 'CrOS')) { return 'cros'; }
34 | if (contains(navigator.platform, 'Linux')) { return 'linux'; }
35 | if (contains(navigator.userAgent, 'iPad') || contains(navigator.platform, 'iPod') || contains(navigator.platform, 'iPhone')) { return 'ios'; }
36 | return '';
37 | } ());
38 |
39 | var browser = (function() {
40 | if (contains(navigator.userAgent, 'Chrome/')) { return 'chrome'; }
41 | if (contains(navigator.vendor, 'Apple')) { return 'safari'; }
42 | if (contains(navigator.userAgent, 'MSIE')) { return 'ie'; }
43 | if (contains(navigator.userAgent, 'Gecko/')) { return 'moz'; }
44 | if (contains(navigator.userAgent, 'Opera/')) { return 'opera'; }
45 | return '';
46 | } ());
47 |
48 | var browser_os = browser + '-' + os;
49 |
50 | function mergeIf(baseTable, select, table) {
51 | if (browser_os === select || browser === select || os === select) {
52 | Object.keys(table).forEach(function(keyCode) {
53 | baseTable[keyCode] = table[keyCode];
54 | });
55 | }
56 | }
57 |
58 | function remap(o, key) {
59 | var r = {};
60 | Object.keys(o).forEach(function(k) {
61 | var item = o[k];
62 | if (key in item) {
63 | r[item[key]] = item;
64 | }
65 | });
66 | return r;
67 | }
68 |
69 | function invert(o) {
70 | var r = {};
71 | Object.keys(o).forEach(function(k) {
72 | r[o[k]] = k;
73 | });
74 | return r;
75 | }
76 |
77 | //--------------------------------------------------------------------
78 | //
79 | // Generic Mappings
80 | //
81 | //--------------------------------------------------------------------
82 |
83 | // "keyInfo" is a dictionary:
84 | // code: string - name from DOM Level 3 KeyboardEvent code Values
85 | // https://dvcs.w3.org/hg/dom3events/raw-file/tip/html/DOM3Events-code.html
86 | // location (optional): number - one of the DOM_KEY_LOCATION values
87 | // keyCap (optional): string - keyboard label in en-US locale
88 | // USB code Usage ID from page 0x07 unless otherwise noted (Informative)
89 |
90 | // Map of keyCode to keyInfo
91 | var keyCodeToInfoTable = {
92 | // 0x01 - VK_LBUTTON
93 | // 0x02 - VK_RBUTTON
94 | 0x03: { code: 'Cancel' }, // [USB: 0x9b] char \x0018 ??? (Not in D3E)
95 | // 0x04 - VK_MBUTTON
96 | // 0x05 - VK_XBUTTON1
97 | // 0x06 - VK_XBUTTON2
98 | 0x06: { code: 'Help' }, // [USB: 0x75] ???
99 | // 0x07 - undefined
100 | 0x08: { code: 'Backspace' }, // [USB: 0x2a] Labelled Delete on Macintosh keyboards.
101 | 0x09: { code: 'Tab' }, // [USB: 0x2b]
102 | // 0x0A-0x0B - reserved
103 | 0X0C: { code: 'Clear' }, // [USB: 0x9c] NumPad Center (Not in D3E)
104 | 0X0D: { code: 'Enter' }, // [USB: 0x28]
105 | // 0x0E-0x0F - undefined
106 |
107 | 0x10: { code: 'Shift' },
108 | 0x11: { code: 'Control' },
109 | 0x12: { code: 'Alt' },
110 | 0x13: { code: 'Pause' }, // [USB: 0x48]
111 | 0x14: { code: 'CapsLock' }, // [USB: 0x39]
112 | 0x15: { code: 'KanaMode' }, // [USB: 0x88] - "HangulMode" for Korean layout
113 | 0x16: { code: 'HangulMode' }, // [USB: 0x90] 0x15 as well in MSDN VK table ???
114 | 0x17: { code: 'JunjaMode' }, // (Not in D3E)
115 | 0x18: { code: 'FinalMode' }, // (Not in D3E)
116 | 0x19: { code: 'KanjiMode' }, // [USB: 0x91] - "HanjaMode" for Korean layout
117 | // 0x1A - undefined
118 | 0x1B: { code: 'Escape' }, // [USB: 0x29]
119 | 0x1C: { code: 'Convert' }, // [USB: 0x8a]
120 | 0x1D: { code: 'NonConvert' }, // [USB: 0x8b]
121 | 0x1E: { code: 'Accept' }, // (Not in D3E)
122 | 0x1F: { code: 'ModeChange' }, // (Not in D3E)
123 |
124 | 0x20: { code: 'Space' }, // [USB: 0x2c]
125 | 0x21: { code: 'PageUp' }, // [USB: 0x4b]
126 | 0x22: { code: 'PageDown' }, // [USB: 0x4e]
127 | 0x23: { code: 'End' }, // [USB: 0x4d]
128 | 0x24: { code: 'Home' }, // [USB: 0x4a]
129 | 0x25: { code: 'ArrowLeft' }, // [USB: 0x50]
130 | 0x26: { code: 'ArrowUp' }, // [USB: 0x52]
131 | 0x27: { code: 'ArrowRight' }, // [USB: 0x4f]
132 | 0x28: { code: 'ArrowDown' }, // [USB: 0x51]
133 | 0x29: { code: 'Select' }, // (Not in D3E)
134 | 0x2A: { code: 'Print' }, // (Not in D3E)
135 | 0x2B: { code: 'Execute' }, // [USB: 0x74] (Not in D3E)
136 | 0x2C: { code: 'PrintScreen' }, // [USB: 0x46]
137 | 0x2D: { code: 'Insert' }, // [USB: 0x49]
138 | 0x2E: { code: 'Delete' }, // [USB: 0x4c]
139 | 0x2F: { code: 'Help' }, // [USB: 0x75] ???
140 |
141 | 0x30: { code: 'Digit0', keyCap: '0' }, // [USB: 0x27] 0)
142 | 0x31: { code: 'Digit1', keyCap: '1' }, // [USB: 0x1e] 1!
143 | 0x32: { code: 'Digit2', keyCap: '2' }, // [USB: 0x1f] 2@
144 | 0x33: { code: 'Digit3', keyCap: '3' }, // [USB: 0x20] 3#
145 | 0x34: { code: 'Digit4', keyCap: '4' }, // [USB: 0x21] 4$
146 | 0x35: { code: 'Digit5', keyCap: '5' }, // [USB: 0x22] 5%
147 | 0x36: { code: 'Digit6', keyCap: '6' }, // [USB: 0x23] 6^
148 | 0x37: { code: 'Digit7', keyCap: '7' }, // [USB: 0x24] 7&
149 | 0x38: { code: 'Digit8', keyCap: '8' }, // [USB: 0x25] 8*
150 | 0x39: { code: 'Digit9', keyCap: '9' }, // [USB: 0x26] 9(
151 | // 0x3A-0x40 - undefined
152 |
153 | 0x41: { code: 'KeyA', keyCap: 'a' }, // [USB: 0x04]
154 | 0x42: { code: 'KeyB', keyCap: 'b' }, // [USB: 0x05]
155 | 0x43: { code: 'KeyC', keyCap: 'c' }, // [USB: 0x06]
156 | 0x44: { code: 'KeyD', keyCap: 'd' }, // [USB: 0x07]
157 | 0x45: { code: 'KeyE', keyCap: 'e' }, // [USB: 0x08]
158 | 0x46: { code: 'KeyF', keyCap: 'f' }, // [USB: 0x09]
159 | 0x47: { code: 'KeyG', keyCap: 'g' }, // [USB: 0x0a]
160 | 0x48: { code: 'KeyH', keyCap: 'h' }, // [USB: 0x0b]
161 | 0x49: { code: 'KeyI', keyCap: 'i' }, // [USB: 0x0c]
162 | 0x4A: { code: 'KeyJ', keyCap: 'j' }, // [USB: 0x0d]
163 | 0x4B: { code: 'KeyK', keyCap: 'k' }, // [USB: 0x0e]
164 | 0x4C: { code: 'KeyL', keyCap: 'l' }, // [USB: 0x0f]
165 | 0x4D: { code: 'KeyM', keyCap: 'm' }, // [USB: 0x10]
166 | 0x4E: { code: 'KeyN', keyCap: 'n' }, // [USB: 0x11]
167 | 0x4F: { code: 'KeyO', keyCap: 'o' }, // [USB: 0x12]
168 |
169 | 0x50: { code: 'KeyP', keyCap: 'p' }, // [USB: 0x13]
170 | 0x51: { code: 'KeyQ', keyCap: 'q' }, // [USB: 0x14]
171 | 0x52: { code: 'KeyR', keyCap: 'r' }, // [USB: 0x15]
172 | 0x53: { code: 'KeyS', keyCap: 's' }, // [USB: 0x16]
173 | 0x54: { code: 'KeyT', keyCap: 't' }, // [USB: 0x17]
174 | 0x55: { code: 'KeyU', keyCap: 'u' }, // [USB: 0x18]
175 | 0x56: { code: 'KeyV', keyCap: 'v' }, // [USB: 0x19]
176 | 0x57: { code: 'KeyW', keyCap: 'w' }, // [USB: 0x1a]
177 | 0x58: { code: 'KeyX', keyCap: 'x' }, // [USB: 0x1b]
178 | 0x59: { code: 'KeyY', keyCap: 'y' }, // [USB: 0x1c]
179 | 0x5A: { code: 'KeyZ', keyCap: 'z' }, // [USB: 0x1d]
180 | 0x5B: { code: 'OSLeft', location: LEFT }, // [USB: 0xe3]
181 | 0x5C: { code: 'OSRight', location: RIGHT }, // [USB: 0xe7]
182 | 0x5D: { code: 'ContextMenu' }, // [USB: 0x65] Context Menu
183 | // 0x5E - reserved
184 | 0x5F: { code: 'Standby' }, // [USB: 0x82] Sleep
185 |
186 | 0x60: { code: 'Numpad0', keyCap: '0', location: NUMPAD }, // [USB: 0x62]
187 | 0x61: { code: 'Numpad1', keyCap: '1', location: NUMPAD }, // [USB: 0x59]
188 | 0x62: { code: 'Numpad2', keyCap: '2', location: NUMPAD }, // [USB: 0x5a]
189 | 0x63: { code: 'Numpad3', keyCap: '3', location: NUMPAD }, // [USB: 0x5b]
190 | 0x64: { code: 'Numpad4', keyCap: '4', location: NUMPAD }, // [USB: 0x5c]
191 | 0x65: { code: 'Numpad5', keyCap: '5', location: NUMPAD }, // [USB: 0x5d]
192 | 0x66: { code: 'Numpad6', keyCap: '6', location: NUMPAD }, // [USB: 0x5e]
193 | 0x67: { code: 'Numpad7', keyCap: '7', location: NUMPAD }, // [USB: 0x5f]
194 | 0x68: { code: 'Numpad8', keyCap: '8', location: NUMPAD }, // [USB: 0x60]
195 | 0x69: { code: 'Numpad9', keyCap: '9', location: NUMPAD }, // [USB: 0x61]
196 | 0x6A: { code: 'NumpadMultiply', keyCap: '*', location: NUMPAD }, // [USB: 0x55]
197 | 0x6B: { code: 'NumpadAdd', keyCap: '+', location: NUMPAD }, // [USB: 0x57]
198 | 0x6C: { code: 'NumpadComma', keyCap: ',', location: NUMPAD }, // [USB: 0x85]
199 | 0x6D: { code: 'NumpadSubtract', keyCap: '-', location: NUMPAD }, // [USB: 0x56]
200 | 0x6E: { code: 'NumpadDecimal', keyCap: '.', location: NUMPAD }, // [USB: 0x63]
201 | 0x6F: { code: 'NumpadDivide', keyCap: '/', location: NUMPAD }, // [USB: 0x54]
202 |
203 | 0x70: { code: 'F1' }, // [USB: 0x3a]
204 | 0x71: { code: 'F2' }, // [USB: 0x3b]
205 | 0x72: { code: 'F3' }, // [USB: 0x3c]
206 | 0x73: { code: 'F4' }, // [USB: 0x3d]
207 | 0x74: { code: 'F5' }, // [USB: 0x3e]
208 | 0x75: { code: 'F6' }, // [USB: 0x3f]
209 | 0x76: { code: 'F7' }, // [USB: 0x40]
210 | 0x77: { code: 'F8' }, // [USB: 0x41]
211 | 0x78: { code: 'F9' }, // [USB: 0x42]
212 | 0x79: { code: 'F10' }, // [USB: 0x43]
213 | 0x7A: { code: 'F11' }, // [USB: 0x44]
214 | 0x7B: { code: 'F12' }, // [USB: 0x45]
215 | 0x7C: { code: 'F13' }, // [USB: 0x68]
216 | 0x7D: { code: 'F14' }, // [USB: 0x69]
217 | 0x7E: { code: 'F15' }, // [USB: 0x6a]
218 | 0x7F: { code: 'F16' }, // [USB: 0x6b]
219 |
220 | 0x80: { code: 'F17' }, // [USB: 0x6c]
221 | 0x81: { code: 'F18' }, // [USB: 0x6d]
222 | 0x82: { code: 'F19' }, // [USB: 0x6e]
223 | 0x83: { code: 'F20' }, // [USB: 0x6f]
224 | 0x84: { code: 'F21' }, // [USB: 0x70]
225 | 0x85: { code: 'F22' }, // [USB: 0x71]
226 | 0x86: { code: 'F23' }, // [USB: 0x72]
227 | 0x87: { code: 'F24' }, // [USB: 0x73]
228 | // 0x88-0x8F - unassigned
229 |
230 | 0x90: { code: 'NumLock', location: NUMPAD }, // [USB: 0x53]
231 | 0x91: { code: 'ScrollLock' }, // [USB: 0x47]
232 | // 0x92-0x96 - OEM specific
233 | // 0x97-0x9F - unassigned
234 |
235 | // NOTE: 0xA0-0xA5 usually mapped to 0x10-0x12 in browsers
236 | 0xA0: { code: 'ShiftLeft', location: LEFT }, // [USB: 0xe1]
237 | 0xA1: { code: 'ShiftRight', location: RIGHT }, // [USB: 0xe5]
238 | 0xA2: { code: 'ControlLeft', location: LEFT }, // [USB: 0xe0]
239 | 0xA3: { code: 'ControlRight', location: RIGHT }, // [USB: 0xe4]
240 | 0xA4: { code: 'AltLeft', location: LEFT }, // [USB: 0xe2]
241 | 0xA5: { code: 'AltRight', location: RIGHT }, // [USB: 0xe6]
242 |
243 | 0xA6: { code: 'BrowserBack' }, // [USB: 0x0c/0x0224]
244 | 0xA7: { code: 'BrowserForward' }, // [USB: 0x0c/0x0225]
245 | 0xA8: { code: 'BrowserRefresh' }, // [USB: 0x0c/0x0227]
246 | 0xA9: { code: 'BrowserStop' }, // [USB: 0x0c/0x0226]
247 | 0xAA: { code: 'BrowserSearch' }, // [USB: 0x0c/0x0221]
248 | 0xAB: { code: 'BrowserFavorites' }, // [USB: 0x0c/0x0228]
249 | 0xAC: { code: 'BrowserHome' }, // [USB: 0x0c/0x0222]
250 | 0xAD: { code: 'VolumeMute' }, // [USB: 0x7f]
251 | 0xAE: { code: 'VolumeDown' }, // [USB: 0x81]
252 | 0xAF: { code: 'VolumeUp' }, // [USB: 0x80]
253 |
254 | 0xB0: { code: 'MediaTrackNext' }, // [USB: 0x0c/0x00b5]
255 | 0xB1: { code: 'MediaTrackPrevious' }, // [USB: 0x0c/0x00b6]
256 | 0xB2: { code: 'MediaStop' }, // [USB: 0x0c/0x00b7]
257 | 0xB3: { code: 'MediaPlayPause' }, // [USB: 0x0c/0x00cd]
258 | 0xB4: { code: 'LaunchMail' }, // [USB: 0x0c/0x018a]
259 | 0xB5: { code: 'MediaSelect' },
260 | 0xB6: { code: 'LaunchApp1' },
261 | 0xB7: { code: 'LaunchApp2' },
262 | // 0xB8-0xB9 - reserved
263 | 0xBA: { code: 'Semicolon', keyCap: ';' }, // [USB: 0x33] ;: (US Standard 101)
264 | 0xBB: { code: 'Equal', keyCap: '=' }, // [USB: 0x2e] =+
265 | 0xBC: { code: 'Comma', keyCap: ',' }, // [USB: 0x36] ,<
266 | 0xBD: { code: 'Minus', keyCap: '-' }, // [USB: 0x2d] -_
267 | 0xBE: { code: 'Period', keyCap: '.' }, // [USB: 0x37] .>
268 | 0xBF: { code: 'Slash', keyCap: '/' }, // [USB: 0x38] /? (US Standard 101)
269 |
270 | 0xC0: { code: 'Backquote', keyCap: '`' }, // [USB: 0x35] `~ (US Standard 101)
271 | // 0xC1-0xCF - reserved
272 |
273 | // 0xD0-0xD7 - reserved
274 | // 0xD8-0xDA - unassigned
275 | 0xDB: { code: 'BracketLeft', keyCap: '[' }, // [USB: 0x2f] [{ (US Standard 101)
276 | 0xDC: { code: 'Backslash', keyCap: '\\' }, // [USB: 0x31] \| (US Standard 101)
277 | 0xDD: { code: 'BracketRight', keyCap: ']' }, // [USB: 0x30] ]} (US Standard 101)
278 | 0xDE: { code: 'Quote', keyCap: '\'' }, // [USB: 0x34] '" (US Standard 101)
279 | // 0xDF - miscellaneous/varies
280 |
281 | // 0xE0 - reserved
282 | // 0xE1 - OEM specific
283 | 0xE2: { code: 'IntlBackslash', keyCap: '\\' }, // [USB: 0x64] \| (UK Standard 102)
284 | // 0xE3-0xE4 - OEM specific
285 | 0xE5: { code: 'Process' }, // (Not in D3E)
286 | // 0xE6 - OEM specific
287 | // 0xE7 - VK_PACKET
288 | // 0xE8 - unassigned
289 | // 0xE9-0xEF - OEM specific
290 |
291 | // 0xF0-0xF5 - OEM specific
292 | 0xF6: { code: 'Attn' }, // [USB: 0x9a] (Not in D3E)
293 | 0xF7: { code: 'CrSel' }, // [USB: 0xa3] (Not in D3E)
294 | 0xF8: { code: 'ExSel' }, // [USB: 0xa4] (Not in D3E)
295 | 0xF9: { code: 'EraseEof' }, // (Not in D3E)
296 | 0xFA: { code: 'Play' }, // (Not in D3E)
297 | 0xFB: { code: 'ZoomToggle' }, // (Not in D3E)
298 | // 0xFC - VK_NONAME - reserved
299 | // 0xFD - VK_PA1
300 | 0xFE: { code: 'Clear' } // [USB: 0x9c] (Not in D3E)
301 | };
302 |
303 | // No legacy keyCode, but listed in D3E:
304 |
305 | // code: usb
306 | // 'IntlHash': 0x070032,
307 | // 'IntlRo': 0x070087,
308 | // 'IntlYen': 0x070089,
309 | // 'NumpadBackspace': 0x0700bb,
310 | // 'NumpadClear': 0x0700d8,
311 | // 'NumpadClearEntry': 0x0700d9,
312 | // 'NumpadMemoryAdd': 0x0700d3,
313 | // 'NumpadMemoryClear': 0x0700d2,
314 | // 'NumpadMemoryRecall': 0x0700d1,
315 | // 'NumpadMemoryStore': 0x0700d0,
316 | // 'NumpadMemorySubtract': 0x0700d4,
317 | // 'NumpadParenLeft': 0x0700b6,
318 | // 'NumpadParenRight': 0x0700b7,
319 |
320 | //--------------------------------------------------------------------
321 | //
322 | // Browser/OS Specific Mappings
323 | //
324 | //--------------------------------------------------------------------
325 |
326 | mergeIf(keyCodeToInfoTable,
327 | 'moz', {
328 | 0x3B: { code: 'Semicolon', keyCap: ';' }, // [USB: 0x33] ;: (US Standard 101)
329 | 0x3D: { code: 'Equal', keyCap: '=' }, // [USB: 0x2e] =+
330 | 0x6B: { code: 'Equal', keyCap: '=' }, // [USB: 0x2e] =+
331 | 0x6D: { code: 'Minus', keyCap: '-' }, // [USB: 0x2d] -_
332 | 0xBB: { code: 'NumpadAdd', keyCap: '+', location: NUMPAD }, // [USB: 0x57]
333 | 0xBD: { code: 'NumpadSubtract', keyCap: '-', location: NUMPAD } // [USB: 0x56]
334 | });
335 |
336 | mergeIf(keyCodeToInfoTable,
337 | 'moz-mac', {
338 | 0x0C: { code: 'NumLock', location: NUMPAD }, // [USB: 0x53]
339 | 0xAD: { code: 'Minus', keyCap: '-' } // [USB: 0x2d] -_
340 | });
341 |
342 | mergeIf(keyCodeToInfoTable,
343 | 'moz-win', {
344 | 0xAD: { code: 'Minus', keyCap: '-' } // [USB: 0x2d] -_
345 | });
346 |
347 | mergeIf(keyCodeToInfoTable,
348 | 'chrome-mac', {
349 | 0x5D: { code: 'OSRight', location: RIGHT } // [USB: 0xe7]
350 | });
351 |
352 | // Windows via Bootcamp (!)
353 | if (0) {
354 | mergeIf(keyCodeToInfoTable,
355 | 'chrome-win', {
356 | 0xC0: { code: 'Quote', keyCap: '\'' }, // [USB: 0x34] '" (US Standard 101)
357 | 0xDE: { code: 'Backslash', keyCap: '\\' }, // [USB: 0x31] \| (US Standard 101)
358 | 0xDF: { code: 'Backquote', keyCap: '`' } // [USB: 0x35] `~ (US Standard 101)
359 | });
360 |
361 | mergeIf(keyCodeToInfoTable,
362 | 'ie', {
363 | 0xC0: { code: 'Quote', keyCap: '\'' }, // [USB: 0x34] '" (US Standard 101)
364 | 0xDE: { code: 'Backslash', keyCap: '\\' }, // [USB: 0x31] \| (US Standard 101)
365 | 0xDF: { code: 'Backquote', keyCap: '`' } // [USB: 0x35] `~ (US Standard 101)
366 | });
367 | }
368 |
369 | mergeIf(keyCodeToInfoTable,
370 | 'safari', {
371 | 0x03: { code: 'Enter' }, // [USB: 0x28] old Safari
372 | 0x19: { code: 'Tab' } // [USB: 0x2b] old Safari for Shift+Tab
373 | });
374 |
375 | mergeIf(keyCodeToInfoTable,
376 | 'ios', {
377 | 0x0A: { code: 'Enter', location: STANDARD } // [USB: 0x28]
378 | });
379 |
380 | mergeIf(keyCodeToInfoTable,
381 | 'safari-mac', {
382 | 0x5B: { code: 'OSLeft', location: LEFT }, // [USB: 0xe3]
383 | 0x5D: { code: 'OSRight', location: RIGHT }, // [USB: 0xe7]
384 | 0xE5: { code: 'KeyQ', keyCap: 'Q' } // [USB: 0x14] On alternate presses, Ctrl+Q sends this
385 | });
386 |
387 | //--------------------------------------------------------------------
388 | //
389 | // Identifier Mappings
390 | //
391 | //--------------------------------------------------------------------
392 |
393 | // Cases where newer-ish browsers send keyIdentifier which can be
394 | // used to disambiguate keys.
395 |
396 | // keyIdentifierTable[keyIdentifier] -> keyInfo
397 |
398 | var keyIdentifierTable = {};
399 | if ('cros' === os) {
400 | keyIdentifierTable['U+00A0'] = { code: 'ShiftLeft', location: LEFT };
401 | keyIdentifierTable['U+00A1'] = { code: 'ShiftRight', location: RIGHT };
402 | keyIdentifierTable['U+00A2'] = { code: 'ControlLeft', location: LEFT };
403 | keyIdentifierTable['U+00A3'] = { code: 'ControlRight', location: RIGHT };
404 | keyIdentifierTable['U+00A4'] = { code: 'AltLeft', location: LEFT };
405 | keyIdentifierTable['U+00A5'] = { code: 'AltRight', location: RIGHT };
406 | }
407 | if ('chrome-mac' === browser_os) {
408 | keyIdentifierTable['U+0010'] = { code: 'ContextMenu' };
409 | }
410 | if ('safari-mac' === browser_os) {
411 | keyIdentifierTable['U+0010'] = { code: 'ContextMenu' };
412 | }
413 | if ('ios' === os) {
414 | // These only generate keyup events
415 | keyIdentifierTable['U+0010'] = { code: 'Function' };
416 |
417 | keyIdentifierTable['U+001C'] = { code: 'ArrowLeft' };
418 | keyIdentifierTable['U+001D'] = { code: 'ArrowRight' };
419 | keyIdentifierTable['U+001E'] = { code: 'ArrowUp' };
420 | keyIdentifierTable['U+001F'] = { code: 'ArrowDown' };
421 |
422 | keyIdentifierTable['U+0001'] = { code: 'Home' }; // [USB: 0x4a] Fn + ArrowLeft
423 | keyIdentifierTable['U+0004'] = { code: 'End' }; // [USB: 0x4d] Fn + ArrowRight
424 | keyIdentifierTable['U+000B'] = { code: 'PageUp' }; // [USB: 0x4b] Fn + ArrowUp
425 | keyIdentifierTable['U+000C'] = { code: 'PageDown' }; // [USB: 0x4e] Fn + ArrowDown
426 | }
427 |
428 | //--------------------------------------------------------------------
429 | //
430 | // Location Mappings
431 | //
432 | //--------------------------------------------------------------------
433 |
434 | // Cases where newer-ish browsers send location/keyLocation which
435 | // can be used to disambiguate keys.
436 |
437 | // locationTable[location][keyCode] -> keyInfo
438 | var locationTable = [];
439 | locationTable[LEFT] = {
440 | 0x10: { code: 'ShiftLeft', location: LEFT }, // [USB: 0xe1]
441 | 0x11: { code: 'ControlLeft', location: LEFT }, // [USB: 0xe0]
442 | 0x12: { code: 'AltLeft', location: LEFT } // [USB: 0xe2]
443 | };
444 | locationTable[RIGHT] = {
445 | 0x10: { code: 'ShiftRight', location: RIGHT }, // [USB: 0xe5]
446 | 0x11: { code: 'ControlRight', location: RIGHT }, // [USB: 0xe4]
447 | 0x12: { code: 'AltRight', location: RIGHT } // [USB: 0xe6]
448 | };
449 | locationTable[NUMPAD] = {
450 | 0x0D: { code: 'NumpadEnter', location: NUMPAD } // [USB: 0x58]
451 | };
452 |
453 | mergeIf(locationTable[NUMPAD], 'moz', {
454 | 0x6D: { code: 'NumpadSubtract', location: NUMPAD }, // [USB: 0x56]
455 | 0x6B: { code: 'NumpadAdd', location: NUMPAD } // [USB: 0x57]
456 | });
457 | mergeIf(locationTable[LEFT], 'moz-mac', {
458 | 0xE0: { code: 'OSLeft', location: LEFT } // [USB: 0xe3]
459 | });
460 | mergeIf(locationTable[RIGHT], 'moz-mac', {
461 | 0xE0: { code: 'OSRight', location: RIGHT } // [USB: 0xe7]
462 | });
463 | mergeIf(locationTable[RIGHT], 'moz-win', {
464 | 0x5B: { code: 'OSRight', location: RIGHT } // [USB: 0xe7]
465 | });
466 |
467 |
468 | mergeIf(locationTable[RIGHT], 'mac', {
469 | 0x5D: { code: 'OSRight', location: RIGHT } // [USB: 0xe7]
470 | });
471 |
472 | mergeIf(locationTable[NUMPAD], 'chrome-mac', {
473 | 0x0C: { code: 'NumLock', location: NUMPAD } // [USB: 0x53]
474 | });
475 |
476 | mergeIf(locationTable[NUMPAD], 'safari-mac', {
477 | 0x0C: { code: 'NumLock', location: NUMPAD }, // [USB: 0x53]
478 | 0xBB: { code: 'NumpadAdd', location: NUMPAD }, // [USB: 0x57]
479 | 0xBD: { code: 'NumpadSubtract', location: NUMPAD }, // [USB: 0x56]
480 | 0xBE: { code: 'NumpadDecimal', location: NUMPAD }, // [USB: 0x63]
481 | 0xBF: { code: 'NumpadDivide', location: NUMPAD } // [USB: 0x54]
482 | });
483 |
484 |
485 | //--------------------------------------------------------------------
486 | //
487 | // Key Values
488 | //
489 | //--------------------------------------------------------------------
490 |
491 | // Mapping from `code` values to `key` values. Values defined at:
492 | // https://dvcs.w3.org/hg/dom3events/raw-file/tip/html/DOM3Events-key.html
493 | // Entries are only provided when `key` differs from `code`. If
494 | // printable, `shiftKey` has the shifted printable character. This
495 | // assumes US Standard 101 layout
496 |
497 | var codeToKeyTable = {
498 | // Modifier Keys
499 | ShiftLeft: { key: 'Shift' },
500 | ShiftRight: { key: 'Shift' },
501 | ControlLeft: { key: 'Control' },
502 | ControlRight: { key: 'Control' },
503 | AltLeft: { key: 'Alt' },
504 | AltRight: { key: 'Alt' },
505 | OSLeft: { key: 'OS' },
506 | OSRight: { key: 'OS' },
507 |
508 | // Whitespace Keys
509 | NumpadEnter: { key: 'Enter' },
510 | Space: { key: ' ' },
511 |
512 | // Printable Keys
513 | Digit0: { key: '0', shiftKey: ')' },
514 | Digit1: { key: '1', shiftKey: '!' },
515 | Digit2: { key: '2', shiftKey: '@' },
516 | Digit3: { key: '3', shiftKey: '#' },
517 | Digit4: { key: '4', shiftKey: '$' },
518 | Digit5: { key: '5', shiftKey: '%' },
519 | Digit6: { key: '6', shiftKey: '^' },
520 | Digit7: { key: '7', shiftKey: '&' },
521 | Digit8: { key: '8', shiftKey: '*' },
522 | Digit9: { key: '9', shiftKey: '(' },
523 | KeyA: { key: 'a', shiftKey: 'A' },
524 | KeyB: { key: 'b', shiftKey: 'B' },
525 | KeyC: { key: 'c', shiftKey: 'C' },
526 | KeyD: { key: 'd', shiftKey: 'D' },
527 | KeyE: { key: 'e', shiftKey: 'E' },
528 | KeyF: { key: 'f', shiftKey: 'F' },
529 | KeyG: { key: 'g', shiftKey: 'G' },
530 | KeyH: { key: 'h', shiftKey: 'H' },
531 | KeyI: { key: 'i', shiftKey: 'I' },
532 | KeyJ: { key: 'j', shiftKey: 'J' },
533 | KeyK: { key: 'k', shiftKey: 'K' },
534 | KeyL: { key: 'l', shiftKey: 'L' },
535 | KeyM: { key: 'm', shiftKey: 'M' },
536 | KeyN: { key: 'n', shiftKey: 'N' },
537 | KeyO: { key: 'o', shiftKey: 'O' },
538 | KeyP: { key: 'p', shiftKey: 'P' },
539 | KeyQ: { key: 'q', shiftKey: 'Q' },
540 | KeyR: { key: 'r', shiftKey: 'R' },
541 | KeyS: { key: 's', shiftKey: 'S' },
542 | KeyT: { key: 't', shiftKey: 'T' },
543 | KeyU: { key: 'u', shiftKey: 'U' },
544 | KeyV: { key: 'v', shiftKey: 'V' },
545 | KeyW: { key: 'w', shiftKey: 'W' },
546 | KeyX: { key: 'x', shiftKey: 'X' },
547 | KeyY: { key: 'y', shiftKey: 'Y' },
548 | KeyZ: { key: 'z', shiftKey: 'Z' },
549 | Numpad0: { key: '0' },
550 | Numpad1: { key: '1' },
551 | Numpad2: { key: '2' },
552 | Numpad3: { key: '3' },
553 | Numpad4: { key: '4' },
554 | Numpad5: { key: '5' },
555 | Numpad6: { key: '6' },
556 | Numpad7: { key: '7' },
557 | Numpad8: { key: '8' },
558 | Numpad9: { key: '9' },
559 | NumpadMultiply: { key: '*' },
560 | NumpadAdd: { key: '+' },
561 | NumpadComma: { key: ',' },
562 | NumpadSubtract: { key: '-' },
563 | NumpadDecimal: { key: '.' },
564 | NumpadDivide: { key: '/' },
565 | Semicolon: { key: ';', shiftKey: ':' },
566 | Equal: { key: '=', shiftKey: '+' },
567 | Comma: { key: ',', shiftKey: '<' },
568 | Minus: { key: '-', shiftKey: '_' },
569 | Period: { key: '.', shiftKey: '>' },
570 | Slash: { key: '/', shiftKey: '?' },
571 | Backquote: { key: '`', shiftKey: '~' },
572 | BracketLeft: { key: '[', shiftKey: '{' },
573 | Backslash: { key: '\\', shiftKey: '|' },
574 | BracketRight: { key: ']', shiftKey: '}' },
575 | Quote: { key: '\'', shiftKey: '"' },
576 | IntlBackslash: { key: '\\', shiftKey: '|' }
577 | };
578 |
579 | mergeIf(codeToKeyTable, 'mac', {
580 | OSLeft: { key: 'Meta' },
581 | OSRight: { key: 'Meta' }
582 | });
583 |
584 | // Corrections for 'key' names in older browsers (e.g. FF36-)
585 | // https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent.key#Key_values
586 | var keyFixTable = {
587 | Esc: 'Escape',
588 | Nonconvert: 'NonConvert',
589 | Left: 'ArrowLeft',
590 | Up: 'ArrowUp',
591 | Right: 'ArrowRight',
592 | Down: 'ArrowDown',
593 | Del: 'Delete',
594 | Menu: 'ContextMenu',
595 | MediaNextTrack: 'MediaTrackNext',
596 | MediaPreviousTrack: 'MediaTrackPrevious',
597 | SelectMedia: 'MediaSelect',
598 | HalfWidth: 'Hankaku',
599 | FullWidth: 'Zenkaku',
600 | RomanCharacters: 'Romaji',
601 | Crsel: 'CrSel',
602 | Exsel: 'ExSel',
603 | Zoom: 'ZoomToggle'
604 | };
605 |
606 | //--------------------------------------------------------------------
607 | //
608 | // Exported Functions
609 | //
610 | //--------------------------------------------------------------------
611 |
612 |
613 | var codeTable = remap(keyCodeToInfoTable, 'code');
614 |
615 | try {
616 | var nativeLocation = nativeKeyboardEvent && ('location' in new KeyboardEvent(''));
617 | } catch (_) {}
618 |
619 | function keyInfoForEvent(event) {
620 | var keyCode = 'keyCode' in event ? event.keyCode : 'which' in event ? event.which : 0;
621 |
622 | var keyInfo = (function(){
623 | if (nativeLocation || 'keyLocation' in event) {
624 | var location = nativeLocation ? event.location : event.keyLocation;
625 | if (location && keyCode in locationTable[location]) {
626 | return locationTable[location][keyCode];
627 | }
628 | }
629 | if ('keyIdentifier' in event && event.keyIdentifier in keyIdentifierTable) {
630 | return keyIdentifierTable[event.keyIdentifier];
631 | }
632 | if (keyCode in keyCodeToInfoTable) {
633 | return keyCodeToInfoTable[keyCode];
634 | }
635 | return null;
636 | }());
637 |
638 | // TODO: Track these down and move to general tables
639 | if (0) {
640 | // TODO: Map these for newerish browsers?
641 | // TODO: iOS only?
642 | // TODO: Override with more common keyIdentifier name?
643 | switch (event.keyIdentifier) {
644 | case 'U+0010': keyInfo = { code: 'Function' }; break;
645 | case 'U+001C': keyInfo = { code: 'ArrowLeft' }; break;
646 | case 'U+001D': keyInfo = { code: 'ArrowRight' }; break;
647 | case 'U+001E': keyInfo = { code: 'ArrowUp' }; break;
648 | case 'U+001F': keyInfo = { code: 'ArrowDown' }; break;
649 | }
650 | }
651 |
652 | if (!keyInfo)
653 | return null;
654 |
655 | var key = (function() {
656 | var entry = codeToKeyTable[keyInfo.code];
657 | if (!entry) return keyInfo.code;
658 | return (event.shiftKey && 'shiftKey' in entry) ? entry.shiftKey : entry.key;
659 | }());
660 |
661 | return {
662 | code: keyInfo.code,
663 | key: key,
664 | location: keyInfo.location,
665 | keyCap: keyInfo.keyCap
666 | };
667 | }
668 |
669 | function queryKeyCap(code, locale) {
670 | code = String(code);
671 | if (!codeTable.hasOwnProperty(code)) return 'Undefined';
672 | if (locale && String(locale).toLowerCase() !== 'en-us') throw Error('Unsupported locale');
673 | var keyInfo = codeTable[code];
674 | return keyInfo.keyCap || keyInfo.code || 'Undefined';
675 | }
676 |
677 | if ('KeyboardEvent' in global && 'defineProperty' in Object) {
678 | (function() {
679 | function define(o, p, v) {
680 | if (p in o) return;
681 | Object.defineProperty(o, p, v);
682 | }
683 |
684 | define(KeyboardEvent.prototype, 'code', { get: function() {
685 | var keyInfo = keyInfoForEvent(this);
686 | return keyInfo ? keyInfo.code : '';
687 | }});
688 |
689 | // Fix for nonstandard `key` values (FF36-)
690 | if ('key' in KeyboardEvent.prototype) {
691 | var desc = Object.getOwnPropertyDescriptor(KeyboardEvent.prototype, 'key');
692 | Object.defineProperty(KeyboardEvent.prototype, 'key', { get: function() {
693 | var key = desc.get.call(this);
694 | return keyFixTable.hasOwnProperty(key) ? keyFixTable[key] : key;
695 | }});
696 | }
697 |
698 | define(KeyboardEvent.prototype, 'key', { get: function() {
699 | var keyInfo = keyInfoForEvent(this);
700 | return (keyInfo && 'key' in keyInfo) ? keyInfo.key : 'Unidentified';
701 | }});
702 |
703 | define(KeyboardEvent.prototype, 'location', { get: function() {
704 | var keyInfo = keyInfoForEvent(this);
705 | return (keyInfo && 'location' in keyInfo) ? keyInfo.location : STANDARD;
706 | }});
707 |
708 | define(KeyboardEvent.prototype, 'locale', { get: function() {
709 | return '';
710 | }});
711 | }());
712 | }
713 |
714 | if (!('queryKeyCap' in global.KeyboardEvent))
715 | global.KeyboardEvent.queryKeyCap = queryKeyCap;
716 |
717 | // Helper for IE8-
718 | global.identifyKey = function(event) {
719 | if ('code' in event)
720 | return;
721 |
722 | var keyInfo = keyInfoForEvent(event);
723 | event.code = keyInfo ? keyInfo.code : '';
724 | event.key = (keyInfo && 'key' in keyInfo) ? keyInfo.key : 'Unidentified';
725 | event.location = ('location' in event) ? event.location :
726 | ('keyLocation' in event) ? event.keyLocation :
727 | (keyInfo && 'location' in keyInfo) ? keyInfo.location : STANDARD;
728 | event.locale = '';
729 | };
730 |
731 | } (window));
732 |
--------------------------------------------------------------------------------
/client/demo/assets/tree2.dae:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CINEMA4D 15.064 COLLADA Exporter
6 |
7 | 2015-08-28T05:43:48Z
8 | 2015-08-28T05:43:48Z
9 |
10 | Y_UP
11 |
12 |
13 |
14 | tex/shadow-circle.png
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | 0.8 0.8 0.8 1
24 |
25 |
26 | 0.2 0.2 0.2 1
27 |
28 |
29 | 0.5
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 | 0.0627451 0.396078 0.513725 1
41 |
42 |
43 | 0.2 0.2 0.2 1
44 |
45 |
46 | 0.5
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 | 0.0235294 0.564706 0.607843 1
58 |
59 |
60 | 0.2 0.2 0.2 1
61 |
62 |
63 | 0.5
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 | 0.164706 0.72549 0.701961 1
75 |
76 |
77 | 0.2 0.2 0.2 1
78 |
79 |
80 | 0.5
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 | 0.654902 0.47451 0.415686 1
92 |
93 |
94 | 0.2 0.2 0.2 1
95 |
96 |
97 | 0.5
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 | ID13
108 |
109 |
110 |
111 |
112 | ID14
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 | 1 1 1 1
122 |
123 |
124 | 0.88
125 |
126 |
127 | 1
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 | -32.0367 -60 32.0367 32.0367 -60 32.0367 32.0367 -60 -32.0367 -32.0367 -60 -32.0367 -32.0367 -57.6676 37.6676 32.0367 -57.6676 37.6676 36.0183 -57.6676 36.0183 37.6676 -57.6676 32.0367 37.6676 -57.6676 -32.0367 36.0183 -57.6676 -36.0183 32.0367 -57.6676 -37.6676 -32.0367 -57.6676 -37.6676 -36.0183 -57.6676 -36.0183 -37.6676 -57.6676 -32.0367 -37.6676 -57.6676 32.0367 -36.0183 -57.6676 36.0183 -32.0367 -52.0367 40 32.0367 -52.0367 40 37.6676 -52.0367 37.6676 40 -52.0367 32.0367 40 -52.0367 -32.0367 37.6676 -52.0367 -37.6676 32.0367 -52.0367 -40 -32.0367 -52.0367 -40 -37.6676 -52.0367 -37.6676 -40 -52.0367 -32.0367 -40 -52.0367 32.0367 -37.6676 -52.0367 37.6676 -32.0367 52.0367 40 32.0367 52.0367 40 37.6676 52.0367 37.6676 40 52.0367 32.0367 40 52.0367 -32.0367 37.6676 52.0367 -37.6676 32.0367 52.0367 -40 -32.0367 52.0367 -40 -37.6676 52.0367 -37.6676 -40 52.0367 -32.0367 -40 52.0367 32.0367 -37.6676 52.0367 37.6676 -32.0367 57.6676 37.6676 32.0367 57.6676 37.6676 36.0183 57.6676 36.0183 37.6676 57.6676 32.0367 37.6676 57.6676 -32.0367 36.0183 57.6676 -36.0183 32.0367 57.6676 -37.6676 -32.0367 57.6676 -37.6676 -36.0183 57.6676 -36.0183 -37.6676 57.6676 -32.0367 -37.6676 57.6676 32.0367 -36.0183 57.6676 36.0183 -32.0367 60 32.0367 32.0367 60 32.0367 32.0367 60 -32.0367 -32.0367 60 -32.0367
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 | -0.0785213 -0.921027 0.381502 0.0785213 -0.921027 0.381502 0 -0.382683 0.92388 0 0 1 0 0.382683 0.92388 -0.0785213 0.921027 0.381502 0.0785213 0.921027 0.381502 0.156558 -0.912487 0.377964 0.357407 -0.357407 0.862856 0.382683 0 0.92388 0.357406 0.357407 0.862856 0.156558 0.912487 0.377964 0.381502 -0.921027 0.0785213 0.377964 -0.912487 0.156558 0.862856 -0.357407 0.357407 0.92388 0 0.382683 0.862856 0.357407 0.357407 0.377964 0.912487 0.156558 0.381502 0.921027 0.0785213 0.381502 -0.921027 -0.0785213 0.92388 -0.382683 -0 1 0 -0 0.92388 0.382683 -0 0.381502 0.921027 -0.0785213 0.377964 -0.912487 -0.156558 0.862856 -0.357407 -0.357407 0.92388 0 -0.382683 0.862856 0.357407 -0.357407 0.377964 0.912487 -0.156558 0.0785213 -0.921027 -0.381502 0.156558 -0.912487 -0.377964 0.357406 -0.357407 -0.862856 0.382683 0 -0.92388 0.357407 0.357407 -0.862856 0.156558 0.912487 -0.377964 0.0785213 0.921027 -0.381502 -0.0785213 -0.921027 -0.381502 0 -0.382683 -0.92388 0 0 -1 0 0.382683 -0.92388 -0.0785213 0.921027 -0.381502 -0.156558 -0.912487 -0.377964 -0.357407 -0.357407 -0.862856 -0.382683 0 -0.92388 -0.357406 0.357407 -0.862856 -0.156558 0.912487 -0.377964 -0.381502 -0.921027 -0.0785213 -0.377964 -0.912487 -0.156558 -0.862856 -0.357407 -0.357407 -0.92388 0 -0.382683 -0.862856 0.357407 -0.357407 -0.377964 0.912487 -0.156558 -0.381502 0.921027 -0.0785213 -0.381502 -0.921027 0.0785213 -0.92388 -0.382683 -0 -1 0 -0 -0.92388 0.382683 -0 -0.381502 0.921027 0.0785213 -0.377964 -0.912487 0.156558 -0.862856 -0.357407 0.357407 -0.92388 0 0.382683 -0.862856 0.357407 0.357407 -0.377964 0.912487 0.156558 -0.156558 -0.912487 0.377964 -0.357406 -0.357407 0.862856 -0.382683 0 0.92388 -0.357407 0.357407 0.862856 -0.156558 0.912487 0.377964 0 1 -0 0 -1 -0
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 | 0 0 0 0.0484494 0.836663 0.0484494 0.836663 0 0 0.0968987 0.836663 0.0968987 0 0.903101 0.836663 0.903101 0 0.951551 0.836663 0.951551 0 1 0.836663 1 0.877497 0 0.918331 0.0484494 0.918331 0.0968987 0.918331 0.903101 0.918331 0.951551 0.877497 1 0.959166 0 1 0.0484494 1 0.0968987 1 0.903101 1 0.951551 0.959166 1 1 1 1 0
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 | 4 4 4 4 4 3 4 4 4 3 3 4 4 4 3 4 4 4 4 4 3 4 4 4 3 3 4 4 4 3 4 4 4 4 4 3 4 4 4 3 3 4 4 4 3 4 4 4 4 4 3 4 4 4 3 3 4 4 4 3 4 4
194 | 1 1 3 5 1 2 4 0 1 0 0 0 5 2 2 17 2 5 16 2 4 4 2 1 17 3 5 29 3 7 28 3 6 16 3 4 29 4 7 41 4 9 40 4 8 28 4 6 41 6 9 53 6 11 52 5 10 40 5 8 6 7 13 5 1 2 1 1 12 6 8 13 18 8 14 17 8 5 5 8 2 18 9 14 30 9 15 29 9 7 17 9 5 30 10 15 42 10 16 41 10 9 29 10 7 42 11 16 53 6 17 41 6 9 7 12 19 6 13 13 1 12 18 7 14 19 19 14 20 18 14 14 6 14 13 19 15 20 31 15 21 30 15 15 18 15 14 31 16 21 43 16 22 42 16 16 30 16 15 43 18 22 53 18 23 42 17 16 2 19 3 8 19 2 7 12 1 1 12 0 8 20 2 20 20 5 19 20 4 7 20 1 20 21 5 32 21 7 31 21 6 19 21 4 32 22 7 44 22 9 43 22 8 31 22 6 44 23 9 54 23 11 53 18 10 43 18 8 9 24 13 8 19 2 2 19 12 9 25 13 21 25 14 20 25 5 8 25 2 21 26 14 33 26 15 32 26 7 20 26 5 33 27 15 45 27 16 44 27 9 32 27 7 45 28 16 54 23 17 44 23 9 10 29 19 9 30 13 2 29 18 10 31 19 22 31 20 21 31 14 9 31 13 22 32 20 34 32 21 33 32 15 21 32 14 34 33 21 46 33 22 45 33 16 33 33 15 46 35 22 54 35 23 45 34 16 3 36 3 11 36 2 10 29 1 2 29 0 11 37 2 23 37 5 22 37 4 10 37 1 23 38 5 35 38 7 34 38 6 22 38 4 35 39 7 47 39 9 46 39 8 34 39 6 47 40 9 55 40 11 54 35 10 46 35 8 12 41 13 11 36 2 3 36 12 12 42 13 24 42 14 23 42 5 11 42 2 24 43 14 36 43 15 35 43 7 23 43 5 36 44 15 48 44 16 47 44 9 35 44 7 48 45 16 55 40 17 47 40 9 13 46 19 12 47 13 3 46 18 13 48 19 25 48 20 24 48 14 12 48 13 25 49 20 37 49 21 36 49 15 24 49 14 37 50 21 49 50 22 48 50 16 36 50 15 49 52 22 55 52 23 48 51 16 0 53 3 14 53 2 13 46 1 3 46 0 14 54 2 26 54 5 25 54 4 13 54 1 26 55 5 38 55 7 37 55 6 25 55 4 38 56 7 50 56 9 49 56 8 37 56 6 50 57 9 52 57 11 55 52 10 49 52 8 15 58 13 14 53 2 0 53 12 15 59 13 27 59 14 26 59 5 14 59 2 27 60 14 39 60 15 38 60 7 26 60 5 39 61 15 51 61 16 50 61 9 38 61 7 51 62 16 52 57 17 50 57 9 4 0 19 15 63 13 0 0 18 4 64 19 16 64 20 27 64 14 15 64 13 16 65 20 28 65 21 39 65 15 27 65 14 28 66 21 40 66 22 51 66 16 39 66 15 40 5 22 52 5 23 51 67 16 53 68 25 54 68 24 55 68 10 52 68 0 3 69 0 2 69 25 1 69 24 0 69 10
195 |
196 |
197 |
198 |
199 |
200 |
201 | -18.4211 -34.5 62.1092 18.4211 -34.5 62.1092 18.4211 -34.5 -62.1092 -18.4211 -34.5 -62.1092 -18.4211 -33.1589 65.3469 18.4211 -33.1589 65.3469 20.7105 -33.1589 64.3986 21.6589 -33.1589 62.1092 21.6589 -33.1589 -62.1092 20.7105 -33.1589 -64.3986 18.4211 -33.1589 -65.3469 -18.4211 -33.1589 -65.3469 -20.7105 -33.1589 -64.3986 -21.6589 -33.1589 -62.1092 -21.6589 -33.1589 62.1092 -20.7105 -33.1589 64.3986 -18.4211 -29.9211 66.6881 18.4211 -29.9211 66.6881 21.6589 -29.9211 65.3469 23 -29.9211 62.1092 23 -29.9211 -62.1092 21.6589 -29.9211 -65.3469 18.4211 -29.9211 -66.6881 -18.4211 -29.9211 -66.6881 -21.6589 -29.9211 -65.3469 -23 -29.9211 -62.1092 -23 -29.9211 62.1092 -21.6589 -29.9211 65.3469 -18.4211 29.9211 66.6881 18.4211 29.9211 66.6881 21.6589 29.9211 65.3469 23 29.9211 62.1092 23 29.9211 -62.1092 21.6589 29.9211 -65.3469 18.4211 29.9211 -66.6881 -18.4211 29.9211 -66.6881 -21.6589 29.9211 -65.3469 -23 29.9211 -62.1092 -23 29.9211 62.1092 -21.6589 29.9211 65.3469 -18.4211 33.1589 65.3469 18.4211 33.1589 65.3469 20.7105 33.1589 64.3986 21.6589 33.1589 62.1092 21.6589 33.1589 -62.1092 20.7105 33.1589 -64.3986 18.4211 33.1589 -65.3469 -18.4211 33.1589 -65.3469 -20.7105 33.1589 -64.3986 -21.6589 33.1589 -62.1092 -21.6589 33.1589 62.1092 -20.7105 33.1589 64.3986 -18.4211 34.5 62.1092 18.4211 34.5 62.1092 18.4211 34.5 -62.1092 -18.4211 34.5 -62.1092
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 | -0.0785212 -0.921027 0.381502 0.0785212 -0.921027 0.381502 0 -0.382683 0.92388 0 0 1 0 0.382683 0.92388 -0.0785212 0.921027 0.381502 0.0785212 0.921027 0.381502 0.156558 -0.912487 0.377965 0.357406 -0.357406 0.862857 0.382683 0 0.92388 0.357406 0.357406 0.862857 0.156558 0.912487 0.377965 0.381502 -0.921027 0.0785213 0.377965 -0.912487 0.156558 0.862856 -0.357407 0.357407 0.923879 0 0.382684 0.862856 0.357406 0.357407 0.377965 0.912487 0.156558 0.381502 0.921027 0.0785213 0.381502 -0.921027 -0.0785213 0.92388 -0.382683 -0 1 0 -0 0.92388 0.382683 -0 0.381502 0.921027 -0.0785213 0.377965 -0.912487 -0.156558 0.862856 -0.357406 -0.357407 0.923879 0 -0.382684 0.862856 0.357407 -0.357407 0.377965 0.912487 -0.156558 0.0785212 -0.921027 -0.381502 0.156558 -0.912487 -0.377965 0.357406 -0.357406 -0.862857 0.382683 0 -0.92388 0.357406 0.357406 -0.862857 0.156558 0.912487 -0.377965 0.0785212 0.921027 -0.381502 -0.0785212 -0.921027 -0.381502 0 -0.382683 -0.92388 0 0 -1 0 0.382683 -0.92388 -0.0785212 0.921027 -0.381502 -0.156558 -0.912487 -0.377965 -0.357406 -0.357406 -0.862857 -0.382683 0 -0.92388 -0.357406 0.357406 -0.862857 -0.156558 0.912487 -0.377965 -0.381502 -0.921027 -0.0785213 -0.377965 -0.912487 -0.156558 -0.862856 -0.357407 -0.357407 -0.923879 0 -0.382684 -0.862856 0.357406 -0.357407 -0.377965 0.912487 -0.156558 -0.381502 0.921027 -0.0785213 -0.381502 -0.921027 0.0785213 -0.92388 -0.382683 -0 -1 0 -0 -0.92388 0.382683 -0 -0.381502 0.921027 0.0785213 -0.377965 -0.912487 0.156558 -0.862856 -0.357406 0.357407 -0.923879 0 0.382684 -0.862856 0.357407 0.357407 -0.377965 0.912487 0.156558 -0.156558 -0.912487 0.377965 -0.357406 -0.357406 0.862857 -0.382683 0 0.92388 -0.357406 0.357406 0.862857 -0.156558 0.912487 0.377965 0 1 -0 0 -1 -0
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 | 0 0 0 0.0484494 0.836663 0.0484494 0.836663 0 0 0.0968987 0.836663 0.0968987 0 0.903101 0.836663 0.903101 0 0.951551 0.836663 0.951551 0 1 0.836663 1 0.877497 0 0.918331 0.0484494 0.918331 0.0968987 0.918331 0.903101 0.918331 0.951551 0.877497 1 0.959166 0 1 0.0484494 1 0.0968987 1 0.903101 1 0.951551 0.959166 1 0.945267 0.0484494 0.945267 0 0.945267 0.0968987 0.945267 0.903101 0.945267 0.951551 0.945267 1 0.95895 0 0.972633 0.0484494 0.972633 0.0968987 0.972633 0.903101 0.972633 0.951551 0.95895 1 0.986317 0 0.986317 1 1 1 1 0
222 |
223 |
224 |
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 |
236 | 4 4 4 4 4 3 4 4 4 3 3 4 4 4 3 4 4 4 4 4 3 4 4 4 3 3 4 4 4 3 4 4 4 4 4 3 4 4 4 3 3 4 4 4 3 4 4 4 4 4 3 4 4 4 3 3 4 4 4 3 4 4
237 | 1 1 3 5 1 2 4 0 1 0 0 0 5 2 2 17 2 5 16 2 4 4 2 1 17 3 5 29 3 7 28 3 6 16 3 4 29 4 7 41 4 9 40 4 8 28 4 6 41 6 9 53 6 11 52 5 10 40 5 8 6 7 13 5 1 2 1 1 12 6 8 13 18 8 14 17 8 5 5 8 2 18 9 14 30 9 15 29 9 7 17 9 5 30 10 15 42 10 16 41 10 9 29 10 7 42 11 16 53 6 17 41 6 9 7 12 19 6 13 13 1 12 18 7 14 19 19 14 20 18 14 14 6 14 13 19 15 20 31 15 21 30 15 15 18 15 14 31 16 21 43 16 22 42 16 16 30 16 15 43 18 22 53 18 23 42 17 16 2 19 25 8 19 24 7 12 1 1 12 0 8 20 24 20 20 26 19 20 4 7 20 1 20 21 26 32 21 27 31 21 6 19 21 4 32 22 27 44 22 28 43 22 8 31 22 6 44 23 28 54 23 29 53 18 10 43 18 8 9 24 31 8 19 24 2 19 30 9 25 31 21 25 32 20 25 26 8 25 24 21 26 32 33 26 33 32 26 27 20 26 26 33 27 33 45 27 34 44 27 28 32 27 27 45 28 34 54 23 35 44 23 28 10 29 19 9 30 31 2 29 36 10 31 19 22 31 20 21 31 32 9 31 31 22 32 20 34 32 21 33 32 33 21 32 32 34 33 21 46 33 22 45 33 34 33 33 33 46 35 22 54 35 37 45 34 34 3 36 3 11 36 2 10 29 1 2 29 0 11 37 2 23 37 5 22 37 4 10 37 1 23 38 5 35 38 7 34 38 6 22 38 4 35 39 7 47 39 9 46 39 8 34 39 6 47 40 9 55 40 11 54 35 10 46 35 8 12 41 13 11 36 2 3 36 12 12 42 13 24 42 14 23 42 5 11 42 2 24 43 14 36 43 15 35 43 7 23 43 5 36 44 15 48 44 16 47 44 9 35 44 7 48 45 16 55 40 17 47 40 9 13 46 19 12 47 13 3 46 18 13 48 19 25 48 20 24 48 14 12 48 13 25 49 20 37 49 21 36 49 15 24 49 14 37 50 21 49 50 22 48 50 16 36 50 15 49 52 22 55 52 23 48 51 16 0 53 25 14 53 24 13 46 1 3 46 0 14 54 24 26 54 26 25 54 4 13 54 1 26 55 26 38 55 27 37 55 6 25 55 4 38 56 27 50 56 28 49 56 8 37 56 6 50 57 28 52 57 29 55 52 10 49 52 8 15 58 31 14 53 24 0 53 30 15 59 31 27 59 32 26 59 26 14 59 24 27 60 32 39 60 33 38 60 27 26 60 26 39 61 33 51 61 34 50 61 28 38 61 27 51 62 34 52 57 35 50 57 28 4 0 19 15 63 31 0 0 36 4 64 19 16 64 20 27 64 32 15 64 31 16 65 20 28 65 21 39 65 33 27 65 32 28 66 21 40 66 22 51 66 34 39 66 33 40 5 22 52 5 37 51 67 34 53 68 39 54 68 38 55 68 10 52 68 0 3 69 0 2 69 39 1 69 38 0 69 10
238 |
239 |
240 |
241 |
242 |
243 |
244 | -40 -80.5 40 40 -80.5 40 40 -80.5 -40 -40 -80.5 -40 -40 -77.5711 47.0711 40 -77.5711 47.0711 45 -77.5711 45 47.0711 -77.5711 40 47.0711 -77.5711 -40 45 -77.5711 -45 40 -77.5711 -47.0711 -40 -77.5711 -47.0711 -45 -77.5711 -45 -47.0711 -77.5711 -40 -47.0711 -77.5711 40 -45 -77.5711 45 -40 -70.5 50 40 -70.5 50 47.0711 -70.5 47.0711 50 -70.5 40 50 -70.5 -40 47.0711 -70.5 -47.0711 40 -70.5 -50 -40 -70.5 -50 -47.0711 -70.5 -47.0711 -50 -70.5 -40 -50 -70.5 40 -47.0711 -70.5 47.0711 -40 70.5 50 40 70.5 50 47.0711 70.5 47.0711 50 70.5 40 50 70.5 -40 47.0711 70.5 -47.0711 40 70.5 -50 -40 70.5 -50 -47.0711 70.5 -47.0711 -50 70.5 -40 -50 70.5 40 -47.0711 70.5 47.0711 -40 77.5711 47.0711 40 77.5711 47.0711 45 77.5711 45 47.0711 77.5711 40 47.0711 77.5711 -40 45 77.5711 -45 40 77.5711 -47.0711 -40 77.5711 -47.0711 -45 77.5711 -45 -47.0711 77.5711 -40 -47.0711 77.5711 40 -45 77.5711 45 -40 80.5 40 40 80.5 40 40 80.5 -40 -40 80.5 -40
245 |
246 |
247 |
248 |
249 |
250 |
251 |
252 |
253 |
254 | -0.0785214 -0.921027 0.381502 0.0785214 -0.921027 0.381502 0 -0.382683 0.92388 0 0 1 0 0.382683 0.92388 -0.0785214 0.921027 0.381502 0.0785214 0.921027 0.381502 0.156558 -0.912487 0.377964 0.357407 -0.357407 0.862856 0.382683 0 0.92388 0.357407 0.357407 0.862856 0.156558 0.912487 0.377964 0.381502 -0.921027 0.0785214 0.377964 -0.912487 0.156558 0.862856 -0.357407 0.357407 0.92388 0 0.382683 0.862856 0.357407 0.357407 0.377964 0.912487 0.156558 0.381502 0.921027 0.0785214 0.381502 -0.921027 -0.0785214 0.92388 -0.382683 -0 1 0 -0 0.92388 0.382683 -0 0.381502 0.921027 -0.0785214 0.377964 -0.912487 -0.156558 0.862856 -0.357407 -0.357407 0.92388 0 -0.382683 0.862856 0.357407 -0.357407 0.377964 0.912487 -0.156558 0.0785214 -0.921027 -0.381502 0.156558 -0.912487 -0.377964 0.357407 -0.357407 -0.862856 0.382683 0 -0.92388 0.357407 0.357407 -0.862856 0.156558 0.912487 -0.377964 0.0785214 0.921027 -0.381502 -0.0785214 -0.921027 -0.381502 0 -0.382683 -0.92388 0 0 -1 0 0.382683 -0.92388 -0.0785214 0.921027 -0.381502 -0.156558 -0.912487 -0.377964 -0.357407 -0.357407 -0.862856 -0.382683 0 -0.92388 -0.357407 0.357407 -0.862856 -0.156558 0.912487 -0.377964 -0.381502 -0.921027 -0.0785214 -0.377964 -0.912487 -0.156558 -0.862856 -0.357407 -0.357407 -0.92388 0 -0.382683 -0.862856 0.357407 -0.357407 -0.377964 0.912487 -0.156558 -0.381502 0.921027 -0.0785214 -0.381502 -0.921027 0.0785214 -0.92388 -0.382683 -0 -1 0 -0 -0.92388 0.382683 -0 -0.381502 0.921027 0.0785214 -0.377964 -0.912487 0.156558 -0.862856 -0.357407 0.357407 -0.92388 0 0.382683 -0.862856 0.357407 0.357407 -0.377964 0.912487 0.156558 -0.156558 -0.912487 0.377964 -0.357407 -0.357407 0.862856 -0.382683 0 0.92388 -0.357407 0.357407 0.862856 -0.156558 0.912487 0.377964 0 1 -0 0 -1 -0
255 |
256 |
257 |
258 |
259 |
260 |
261 |
262 |
263 |
264 | 0 0 0 0.0455526 0.835876 0.0455526 0.835876 0 0 0.0911051 0.835876 0.0911051 0 0.908895 0.835876 0.908895 0 0.954447 0.835876 0.954447 0 1 0.835876 1 0.876907 0 0.917938 0.0455526 0.917938 0.0911051 0.917938 0.908895 0.917938 0.954447 0.876907 1 0.958969 0 1 0.0455526 1 0.0911051 1 0.908895 1 0.954447 0.958969 1 1 1 1 0
265 |
266 |
267 |
268 |
269 |
270 |
271 |
272 |
273 |
274 |
275 |
276 |
277 |
278 |
279 | 4 4 4 4 4 3 4 4 4 3 3 4 4 4 3 4 4 4 4 4 3 4 4 4 3 3 4 4 4 3 4 4 4 4 4 3 4 4 4 3 3 4 4 4 3 4 4 4 4 4 3 4 4 4 3 3 4 4 4 3 4 4
280 | 1 1 3 5 1 2 4 0 1 0 0 0 5 2 2 17 2 5 16 2 4 4 2 1 17 3 5 29 3 7 28 3 6 16 3 4 29 4 7 41 4 9 40 4 8 28 4 6 41 6 9 53 6 11 52 5 10 40 5 8 6 7 13 5 1 2 1 1 12 6 8 13 18 8 14 17 8 5 5 8 2 18 9 14 30 9 15 29 9 7 17 9 5 30 10 15 42 10 16 41 10 9 29 10 7 42 11 16 53 6 17 41 6 9 7 12 19 6 13 13 1 12 18 7 14 19 19 14 20 18 14 14 6 14 13 19 15 20 31 15 21 30 15 15 18 15 14 31 16 21 43 16 22 42 16 16 30 16 15 43 18 22 53 18 23 42 17 16 2 19 3 8 19 2 7 12 1 1 12 0 8 20 2 20 20 5 19 20 4 7 20 1 20 21 5 32 21 7 31 21 6 19 21 4 32 22 7 44 22 9 43 22 8 31 22 6 44 23 9 54 23 11 53 18 10 43 18 8 9 24 13 8 19 2 2 19 12 9 25 13 21 25 14 20 25 5 8 25 2 21 26 14 33 26 15 32 26 7 20 26 5 33 27 15 45 27 16 44 27 9 32 27 7 45 28 16 54 23 17 44 23 9 10 29 19 9 30 13 2 29 18 10 31 19 22 31 20 21 31 14 9 31 13 22 32 20 34 32 21 33 32 15 21 32 14 34 33 21 46 33 22 45 33 16 33 33 15 46 35 22 54 35 23 45 34 16 3 36 3 11 36 2 10 29 1 2 29 0 11 37 2 23 37 5 22 37 4 10 37 1 23 38 5 35 38 7 34 38 6 22 38 4 35 39 7 47 39 9 46 39 8 34 39 6 47 40 9 55 40 11 54 35 10 46 35 8 12 41 13 11 36 2 3 36 12 12 42 13 24 42 14 23 42 5 11 42 2 24 43 14 36 43 15 35 43 7 23 43 5 36 44 15 48 44 16 47 44 9 35 44 7 48 45 16 55 40 17 47 40 9 13 46 19 12 47 13 3 46 18 13 48 19 25 48 20 24 48 14 12 48 13 25 49 20 37 49 21 36 49 15 24 49 14 37 50 21 49 50 22 48 50 16 36 50 15 49 52 22 55 52 23 48 51 16 0 53 3 14 53 2 13 46 1 3 46 0 14 54 2 26 54 5 25 54 4 13 54 1 26 55 5 38 55 7 37 55 6 25 55 4 38 56 7 50 56 9 49 56 8 37 56 6 50 57 9 52 57 11 55 52 10 49 52 8 15 58 13 14 53 2 0 53 12 15 59 13 27 59 14 26 59 5 14 59 2 27 60 14 39 60 15 38 60 7 26 60 5 39 61 15 51 61 16 50 61 9 38 61 7 51 62 16 52 57 17 50 57 9 4 0 19 15 63 13 0 0 18 4 64 19 16 64 20 27 64 14 15 64 13 16 65 20 28 65 21 39 65 15 27 65 14 28 66 21 40 66 22 51 66 16 39 66 15 40 5 22 52 5 23 51 67 16 53 68 25 54 68 24 55 68 10 52 68 0 3 69 0 2 69 25 1 69 24 0 69 10
281 |
282 |
283 |
284 |
285 |
286 |
287 | 0 -50 0 0 50 0 10 -50 -0 10 -50 -0 10 50 -0 10 50 -0 7.07107 -50 -7.07107 7.07107 -50 -7.07107 7.07107 50 -7.07107 7.07107 50 -7.07107 6.12323e-16 -50 -10 6.12323e-16 -50 -10 6.12323e-16 50 -10 6.12323e-16 50 -10 -7.07107 -50 -7.07107 -7.07107 -50 -7.07107 -7.07107 50 -7.07107 -7.07107 50 -7.07107 -10 -50 -1.22465e-15 -10 -50 -1.22465e-15 -10 50 -1.22465e-15 -10 50 -1.22465e-15 -7.07107 -50 7.07107 -7.07107 -50 7.07107 -7.07107 50 7.07107 -7.07107 50 7.07107 -1.83697e-15 -50 10 -1.83697e-15 -50 10 -1.83697e-15 50 10 -1.83697e-15 50 10 7.07107 -50 7.07107 7.07107 -50 7.07107 7.07107 50 7.07107 7.07107 50 7.07107
288 |
289 |
290 |
291 |
292 |
293 |
294 |
295 |
296 |
297 | 0 -1 -0 1 0 -0 0.707107 0 -0.707107 0 1 -0 0 0 -1 -0.707107 0 -0.707107 -1 0 -0 -0.707107 0 0.707107 0 0 1 0.707107 0 0.707107
298 |
299 |
300 |
301 |
302 |
303 |
304 |
305 |
306 |
307 | 0.5 0.5 0 0.5 0.146447 0.853553 0 0 0 1 0.125 1 0.125 0 1 0.5 0.853553 0.853553 0.5 1 0.25 1 0.25 0 0.375 1 0.375 0 0.5 0 0.853553 0.146447 0.625 1 0.625 0 0.146447 0.146447 0.75 1 0.75 0 0.875 1 0.875 0 1 1 1 0
308 |
309 |
310 |
311 |
312 |
313 |
314 |
315 |
316 |
317 |
318 |
319 |
320 |
321 |
322 | 3 4 3 3 4 3 3 4 3 3 4 3 3 4 3 3 4 3 3 4 3 3 4 3
323 | 6 0 2 2 0 1 0 0 0 7 2 6 8 2 5 4 1 4 3 1 3 9 3 8 1 3 0 5 3 7 10 0 9 6 0 2 0 0 0 11 4 11 12 4 10 8 2 5 7 2 6 13 3 9 1 3 0 9 3 8 14 0 8 10 0 9 0 0 0 15 5 13 16 5 12 12 4 10 11 4 11 17 3 2 1 3 0 13 3 9 18 0 7 14 0 8 0 0 0 19 6 14 20 6 9 16 5 12 15 5 13 21 3 1 1 3 0 17 3 2 22 0 15 18 0 7 0 0 0 23 7 17 24 7 16 20 6 9 19 6 14 25 3 18 1 3 0 21 3 1 26 0 14 22 0 15 0 0 0 27 8 20 28 8 19 24 7 16 23 7 17 29 3 14 1 3 0 25 3 18 30 0 18 26 0 14 0 0 0 31 9 22 32 9 21 28 8 19 27 8 20 33 3 15 1 3 0 29 3 14 2 0 1 30 0 18 0 0 0 3 1 24 4 1 23 32 9 21 31 9 22 5 3 7 1 3 0 33 3 15
324 |
325 |
326 |
327 |
328 |
329 |
330 | -50 0 50 50 0 50 -50 0 -50 50 0 -50
331 |
332 |
333 |
334 |
335 |
336 |
337 |
338 |
339 |
340 | 0 1 -0
341 |
342 |
343 |
344 |
345 |
346 |
347 |
348 |
349 |
350 | 0 0 0 1 1 1 1 0
351 |
352 |
353 |
354 |
355 |
356 |
357 |
358 |
359 |
360 |
361 |
362 |
363 |
364 |
365 | 4
366 | 1 0 3 3 0 2 2 0 1 0 0 0
367 |
368 |
369 |
370 |
371 |
372 |
373 |
374 | -23.7123 210.709 24.9106
375 | 0 1 0 -0
376 | 1 0 0 0
377 | 0 0 1 -0
378 | 1 1 1
379 |
380 |
381 |
382 |
383 |
384 |
385 |
386 |
387 |
388 |
389 |
390 | 3.20043 174.503 2.82738
391 | 0 1 0 -0
392 | 1 0 0 0
393 | 0 0 1 -0
394 | 1 1 1
395 |
396 |
397 |
398 |
399 |
400 |
401 |
402 |
403 |
404 |
405 |
406 | 0 180 -0
407 | 0 1 0 -0
408 | 1 0 0 0
409 | 0 0 1 -0
410 | 1 1 1
411 |
412 |
413 |
414 |
415 |
416 |
417 |
418 |
419 |
420 |
421 |
422 | 0 50 -0
423 | 0 1 0 -0
424 | 1 0 0 0
425 | 0 0 1 -0
426 | 1 1 1
427 |
428 |
429 |
430 |
431 |
432 |
433 |
434 |
435 |
436 |
437 |
438 | 0 0 -0
439 | 0 1 0 -0
440 | 1 0 0 0
441 | 0 0 1 -0
442 | 1 1 1
443 |
444 |
445 |
446 |
447 |
448 |
449 |
450 |
451 |
452 |
453 |
454 |
455 |
456 |
457 |
458 |
459 |
--------------------------------------------------------------------------------