diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 000000000..d9d24da24 --- /dev/null +++ b/.eslintignore @@ -0,0 +1,4 @@ +**/ReactVR/js/defs/* +website/ +**/*.bundle.js +**/node_modules/* diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 000000000..d2f552eac --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,41 @@ +module.exports = { + extends: [ + 'fbjs', + 'prettier' + ], + + plugins: [ + "prettier", + ], + + rules: { + 'accessor-pairs': 'off', + 'consistent-return': 'off', + 'eqeqeq': 'error', + 'key-spacing': ['warn', {beforeColon: false, afterColon: true}], + 'max-len': ['warn', {code: 120, ignoreComments: true}], + 'no-bitwise': 'off', + 'no-tabs': 'warn', + 'no-var': 'warn', + 'object-curly-spacing': ['warn', 'never'], + 'prefer-const': ['warn', {destructuring: 'all'}], + 'space-in-parens': 'warn', + + // Prettier + "prettier/prettier": [ + "error", + { + "bracketSpacing": false, + "jsxBracketSameLine": true, + "singleQuote": true, + "trailingComma": "es5", + "printWidth": 100, + } + ] + }, + globals: { + '__DEV__': true, + '__dirname': false, + '__fbBatchedBridgeConfig': false, + } +} diff --git a/.flowconfig b/.flowconfig new file mode 100644 index 000000000..ac13ffd93 --- /dev/null +++ b/.flowconfig @@ -0,0 +1,19 @@ +[ignore] +.*/Libraries/.* +.*/docs/.* +.*/EndToEnd/.* +.*/jest/.* +.*/website/.* +.*/__tests__/.* +/node_modules/.* + +[include] + +[libs] +ReactVR/js/defs/ + +[options] +emoji=true + +module.ignore_non_literal_requires=true +unsafe.enable_getters_and_setters=true diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..dfdb8b771 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +*.sh text eol=lf diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 000000000..63d7fab74 --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,27 @@ +We use GitHub Issues for bugs and support tracking for the preview + +--- Please use this template, and delete everything above this line before submitting your issue --- + +### Description + +[FILL THIS OUT: Bug or Feature Request? For a Bug please explain what you did, what you expected to happen, and what actually happens. Sections below] + +#### Expected behavior + +#### Actual behavior + +### Reproduction + +[FILL THIS OUT: Try to reproduce your bug in a minimal example, maybe you could alter one of the samples to demonstrate the problem] + +### Solution + +[FILL THIS OUT: What needs to be done to address this issue? Ideally, provide source code or a patch with a fix.] + +### Additional Information + +* React VR version: [FILL THIS OUT: both react-vr and react-vr-web] +* Operating System: [FILL THIS OUT: MacOS, Linux, or Windows?] +* Graphics Card: [FILL THIS OUT: NVIDA, ATI, Intel? Which Driver?] +* Browser: [FILL THIS OUT: Carmel, Chrome, Edge, Safari? Which version?] +* VR Device: [FILL THIS OUT: In or Out of VR? Headset? ] diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 000000000..16ad44a59 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,30 @@ +Thanks for submitting a PR! Please read these instructions carefully: + +- [ ] Explain the **motivation** for making this change. +- [ ] Provide a **test plan** demonstrating that the code is solid. +- [ ] Match the **code formatting** of the rest of the codebase. + +## Motivation (required) + +What existing problem does the pull request solve? + +## Test Plan (required) + +A good test plan has the exact commands you ran and their output, provides screenshots or videos if the pull request changes UI or updates the website. See [What is a Test Plan?][1] to learn more. + +If you have added code that should be tested, add tests. + +## Next Steps + +Sign the [CLA][2], if you haven't already. + +Small pull requests are much easier to review and more likely to get merged. Make sure the PR does only one thing, otherwise please split it. + +Make sure all **tests pass** on [Circle CI][3]. PRs that break tests are unlikely to be merged. + +For more info, see the ["Pull Requests"][4] section of our "Contributing" guidelines. + +[1]: https://medium.com/@martinkonicek/what-is-a-test-plan-8bfc840ec171#.y9lcuqqi9 +[2]: https://code.facebook.com/cla +[3]: http://circleci.com/gh/facebook/react-native +[4]: https://github.com/facebook/react-vr/blob/master/CONTRIBUTING.md#pull-requests diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..253e40ff0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +coverage/ +node_modules +*~ +.DS_Store +*.log +EndToEnd/testapp/build diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..ad5f9b48b --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,42 @@ +# Contributing to React VR + +React VR is actively being developed by Oculus and Facebook, and is used to power a growing number of internal and external applications. We've put together this document to help make the public contribution process clearer and answer any questions you may have. + +## Pull Requests + +Our core team will be monitoring for pull requests. New pull requests will automatically run against our continuous integration suite, which should detect the majority of formatting and testing issues. After this, one of our team members will run some Facebook-specific integration tests on it, to make sure it doesn't break any of our applications. Once that has completed, one member of the team will sign off on the changes and merge the pull request. Any API changes might require us to fix internal uses, which could cause some delay. We'll do our best to provide updates and feedback in a timely manner throughout the process. + +Before submitting a pull request, please make sure you have done the following: + +1. Fork the repository and fork your working branch from master +2. Describe your test plan in your Pull Request + - If you've added new features, cover them with tests + - If you've changed APIs, update the appropriate documentation +3. Ensure all test suites pass (`npm test`) +4. Ensure the linter doesn't return any errors (`npm run lint`) +5. Ensure that Flow typechecking is sound (`npm run flow`) +6. Make sure you've completed the CLA. + +## Copyright Notice for Files + +Copy and paste this to the tope of your new files: + +``` +/** + * Copyright (c) 2017-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. + */ +``` + +## Contributor License Agreement (CLA) + +In order to accept your pull request, we need you to submit a CLA. You only need to do this once, so if you've done this for another Facebook open source project, you're good to go. If you are submitting a pull request for the first time, just let us know that you have completed the CLA and we can cross-check with your GitHub username. + +[Complete your CLA here](https://code.facebook.com/cla) + +## License + +By contributing to React, you agree that your contributions will be licensed under its BSD license. diff --git a/EndToEnd/__tests__/Bridge-test.js b/EndToEnd/__tests__/Bridge-test.js new file mode 100644 index 000000000..228df0cf2 --- /dev/null +++ b/EndToEnd/__tests__/Bridge-test.js @@ -0,0 +1,58 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +jest + .dontMock('../runner/MockBlob') + .dontMock('../runner/MockURL') + .dontMock('../runner/MockWorker') + .dontMock('../runner/Runner') + .dontMock('../runner/MockCanvas') + .dontMock('path') + .mock('canvas', () => require('../runner/MockCanvas').default, {virtual: true}); + +const Runner = require('../runner/Runner').default; +const path = require('path'); + +process.chdir(path.resolve(__dirname, '..')); + +const html = ` + + + + + + +`; + +describe('React Bridge', () => { + it('exposes Native Modules', () => { + const runner = new Runner(html, {}); + return runner.loaded.then((window) => { + const workerWindow = window.vr.rootView.context.worker.sandbox.window; + expect(workerWindow.NativeModules).toBeTruthy(); + const vrConstants = workerWindow.NativeModules.ReactVRConstants; + expect(vrConstants.Runtime).toBe('WebVR'); + }); + }); + + it('constructs the scene', () => { + const runner = new Runner(html, {}); + return runner.loaded.then((window) => { + const top = window.vr.scene.children[0].children[0]; + expect(top).toBeTruthy(); + expect(top.type).toBe('UIView'); + expect(top.children[0].type).toBe('SDFText'); + }); + }); +}); diff --git a/EndToEnd/__tests__/Runner-test.js b/EndToEnd/__tests__/Runner-test.js new file mode 100644 index 000000000..2eec26f9b --- /dev/null +++ b/EndToEnd/__tests__/Runner-test.js @@ -0,0 +1,118 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +jest + .dontMock('../runner/MockBlob') + .dontMock('../runner/MockURL') + .dontMock('../runner/MockWorker') + .dontMock('../runner/Runner'); + +const Runner = require('../runner/Runner').default; + +process.chdir(__dirname); + +describe('End to End Test Runner', () => { + it('loads injected contents', () => { + const runner = new Runner( + ``, + { + '/mocked.js': 'window.mockedLoaded = true;', + } + ); + return runner.loaded.then((window) => { + expect(window.mockedLoaded).toBe(true); + }); + }); + + it('loads contents from the filesystem', () => { + const runner = new Runner( + ``, + {} + ); + return runner.loaded.then((window) => { + expect(window.loadedFile).toBe(true); + }); + }); + + it('loads injected contents in the worker', () => { + const runner = new Runner( + ` + + +`, + {'worker.js': 'self.workerLoaded = true;'}, + ); + return runner.loaded.then((window) => { + expect(window.worker.sandbox.workerLoaded).toBe(true); + }); + }); + + it('loads filesystem contents in the worker', () => { + const runner = new Runner( + ` + + +`, + {}, + ); + return runner.loaded.then((window) => { + expect(window.worker.sandbox.loadedFile).toBe(true); + }); + }); + + it('loads injected content via importScripts', () => { + const runner = new Runner( + ` + + +`, + {'worker.js': 'importScripts("part2.js")', 'part2.js': 'self.part2loaded = true;'}, + ); + return runner.loaded.then((window) => { + expect(window.worker.sandbox.part2loaded).toBe(true); + }); + }); + + it('loads filesystem content via importScripts', () => { + const runner = new Runner( + ` + + +`, + {'worker.js': 'importScripts("loadingtest.res.js")'}, + ); + return runner.loaded.then((window) => { + expect(window.worker.sandbox.loadedFile).toBe(true); + }); + }); + + it('can load worker scripts from Blobs', () => { + const runner = new Runner( + ` + + +`, + {}, + ); + return runner.loaded.then((window) => { + expect(window.worker.sandbox.blobLoaded).toBe(true); + }); + }); +}); diff --git a/EndToEnd/__tests__/Worker-test.js b/EndToEnd/__tests__/Worker-test.js new file mode 100644 index 000000000..8ad4bb4e0 --- /dev/null +++ b/EndToEnd/__tests__/Worker-test.js @@ -0,0 +1,78 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +jest + .dontMock('../runner/MockBlob') + .dontMock('../runner/MockURL') + .dontMock('../runner/MockWorker') + .dontMock('../runner/Runner'); + +const Runner = require('../runner/Runner').default; + +process.chdir(__dirname); + +describe('End to End Web Worker', () => { + it('sets up globals correctly', () => { + const runner = new Runner( + ` + + +`, + {'worker.js': 'window.a = 1; self.b = 2;'}, + ); + return runner.loaded.then((window) => { + expect(window.worker.sandbox.a).toBe(1); + expect(window.worker.sandbox.b).toBe(2); + }); + }); + + it('can receive messages', () => { + const runner = new Runner( + ` + + +`, + {'worker.js': 'onmessage = function(e) {data = e.data}'}, + ); + return runner.loaded.then((window) => { + expect(window.worker.sandbox.data).toBe('hello'); + }); + }); + + it('can send message', () => { + const runner = new Runner( + ` + + +`, + {'worker.js': ` +self.onmessage = function(e) { + if (e.data === 'ping') { + self.postMessage('pong'); + } +};` + }); + return runner.loaded.then((window) => { + expect(window.pongReceived).toBe(true); + }); + }); +}); diff --git a/EndToEnd/__tests__/loadingtest.res.js b/EndToEnd/__tests__/loadingtest.res.js new file mode 100644 index 000000000..d6edd9c36 --- /dev/null +++ b/EndToEnd/__tests__/loadingtest.res.js @@ -0,0 +1 @@ +window.loadedFile = true; diff --git a/EndToEnd/runner/MockBlob.js b/EndToEnd/runner/MockBlob.js new file mode 100644 index 000000000..151a2f1de --- /dev/null +++ b/EndToEnd/runner/MockBlob.js @@ -0,0 +1,14 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +export default class Blob { + constructor(parts) { + this.__value = parts.join(''); + } +} diff --git a/EndToEnd/runner/MockCanvas.js b/EndToEnd/runner/MockCanvas.js new file mode 100644 index 000000000..d52e42981 --- /dev/null +++ b/EndToEnd/runner/MockCanvas.js @@ -0,0 +1,12 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +export default class Canvas { + +} diff --git a/EndToEnd/runner/MockURL.js b/EndToEnd/runner/MockURL.js new file mode 100644 index 000000000..d5ba4eff9 --- /dev/null +++ b/EndToEnd/runner/MockURL.js @@ -0,0 +1,35 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +import MockBlob from './MockBlob'; + +const objMapping = {}; +let objCount = 0; + +export function _getObjectFromURL(url) { + return objMapping[url]; +} + +export function createObjectURL(obj) { + let url; + if (obj instanceof MockBlob) { + url = 'blob:' + objCount; + objCount++; + } else { + throw new Error('Unsupported object type'); + } + objMapping[url] = obj; + return url; +} + +export function revokeObjectURL(url) { + if (objMapping[url]) { + delete objMapping[url]; + } +} diff --git a/EndToEnd/runner/MockWorker.js b/EndToEnd/runner/MockWorker.js new file mode 100644 index 000000000..ba6f7b53a --- /dev/null +++ b/EndToEnd/runner/MockWorker.js @@ -0,0 +1,78 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +import fs from 'fs'; +import path from 'path'; +import vm from 'vm'; +import {_getObjectFromURL} from './MockURL'; + +export default function(resourceLoader) { + return class { + constructor(source) { + let src; + const sandbox = { + console: { + log: console.log, + warn: console.warn, + error: console.error, + }, + }; + this.sandbox = sandbox; + + const importScripts = function(...scripts) { + for (let i = 0; i < scripts.length; i++) { + resourceLoader({ + url: {pathname: scripts[i]}, + defaultFetch(cb) {}, + }, function(err, code) { + vm.runInContext(code, sandbox); + }); + } + }; + const postMessage = (data) => { + if (typeof this.onmessage === 'function') { + this.onmessage({data}); + } + }; + sandbox.self = sandbox; + sandbox.window = sandbox; + sandbox.importScripts = importScripts; + sandbox.postMessage = postMessage; + + vm.createContext(sandbox); + + if (typeof source === 'string') { + if (source.match(/^blob:/)) { + src = _getObjectFromURL(source).__value; + } else { + resourceLoader({ + url: {pathname: source}, + defaultFetch(cb) {}, + }, function(err, code) { + vm.runInContext(code, sandbox); + }); + } + } + + if (src) { + vm.runInContext(src, sandbox); + } + } + + postMessage(data) { + if (typeof this.sandbox.onmessage === 'function') { + this.sandbox.onmessage({data}); + } + } + + terminate() { + + } + } +} diff --git a/EndToEnd/runner/Runner.js b/EndToEnd/runner/Runner.js new file mode 100644 index 000000000..b85e14638 --- /dev/null +++ b/EndToEnd/runner/Runner.js @@ -0,0 +1,67 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +import fs from 'fs'; +import jsdom from 'jsdom'; +import path from 'path'; +import MockBlob from './MockBlob'; +import * as MockURL from './MockURL'; +import MockWorker from './MockWorker'; + +export default class Runner { + constructor(html, filemap) { + const resourceLoader = function(resource, callback) { + const pathname = resource.url.pathname.replace(/^http:\/\/localhost:8081\//, ''); + if (filemap[pathname]) { + callback(null, filemap[pathname]); + } else { + callback(null, fs.readFileSync(path.resolve('.', pathname.replace(/^\//, '')), 'utf8')); + } + }; + this.loaded = new Promise((resolve) => { + this.dom = jsdom.env({ + html: html, + url: 'http://localhost:8081/', + created: function(err, win) { + win.Blob = MockBlob; + win.URL = MockURL; + win.Worker = MockWorker(resourceLoader); + + // Polyfill rAF + (function(w) { + if (w.requestAnimationFrame) { + return; + } + let lastTime = 0; + w.requestAnimationFrame = function(cb) { + const now = new Date().getTime(); + const delta = Math.max(0, 16 - (now - lastTime)); + const id = window.setTimeout(() => cb(now + delta), delta); + lastTime = now + delta; + return id; + }; + w.cancelAnimationFrame = function(id) { + clearTimeout(id); + }; + }(win)); + }, + done: function(err, win) { + resolve(win); + }, + resourceLoader: resourceLoader, + virtualConsole: jsdom.createVirtualConsole().sendTo(console), + features: { + FetchExternalResources: ['script'], + ProcessExternalResources: ['script'], + SkipExternalResources: false, + }, + }); + }); + } +} diff --git a/EndToEnd/testapp/client.js b/EndToEnd/testapp/client.js new file mode 100644 index 000000000..73f06060a --- /dev/null +++ b/EndToEnd/testapp/client.js @@ -0,0 +1,15 @@ +import {VRInstance} from 'react-vr-web'; + +function init(bundle, parent, options) { + const vr = new VRInstance(bundle, 'End2End', parent, { + ...options, + }); + vr.render = function() { + }; + // Begin the animation loop + vr.start(); + window.vr = vr; + return vr; +} + +window.ReactVR = {init}; diff --git a/EndToEnd/testapp/e2e.vr.js b/EndToEnd/testapp/e2e.vr.js new file mode 100644 index 000000000..1c61a4533 --- /dev/null +++ b/EndToEnd/testapp/e2e.vr.js @@ -0,0 +1,37 @@ +'use strict'; + +import React from 'react'; +import { + AppRegistry, + asset, + NativeModules, + Pano, + Text, + View, +} from 'react-vr'; + +window.NativeModules = NativeModules; + +class End2End extends React.Component { + render() { + return ( + + + hello + + + ); + } +}; + +AppRegistry.registerComponent('End2End', () => End2End); diff --git a/EndToEnd/testapp/rn-cli.config.js b/EndToEnd/testapp/rn-cli.config.js new file mode 100644 index 000000000..1f2b87a07 --- /dev/null +++ b/EndToEnd/testapp/rn-cli.config.js @@ -0,0 +1,47 @@ +'use strict'; + +var path = require('path'); +var blacklist = require('react-native/packager/blacklist'); +console.log(path.resolve('react-native')); + +/** + * Default configuration for the CLI. + * + * If you need to override any of this functions do so by defining the file + * `rn-cli.config.js` on the root of your project with the functions you need + * to tweak. + */ +var config = { + getProjectRoots() { + return getRoots(); + }, + + getBlacklistRE() { + return blacklist([]); + }, + + getAssetExts() { + return [ + 'obj', 'mtl', + ]; + }, + + getPlatforms() { + return ['vr']; + }, + + getProvidesModuleNodeModules() { + return ['react-native', 'react-vr']; + }, +}; + +function getRoots() { + var root = process.env.REACT_NATIVE_APP_ROOT; + if (root) { + return [path.resolve(root)]; + } + return [path.resolve(__dirname, '..', '..')]; +} + +module.exports = config; + diff --git a/Examples/Controller/index.vr.js b/Examples/Controller/index.vr.js new file mode 100755 index 000000000..5fff9801f --- /dev/null +++ b/Examples/Controller/index.vr.js @@ -0,0 +1,228 @@ +'use strict'; + +import React from 'react'; +import {AppRegistry, asset, NativeModules, Pano, StyleSheet, Text, View} from 'react-vr'; + +const RCTDeviceEventEmitter = require('RCTDeviceEventEmitter'); + +class PressState extends React.Component { + constructor() { + super(); + this.state = {hasFocus: false}; + } + + shouldComponentUpdate(nextProps, nextState) { + if (nextProps.pressed !== this.props.pressed) { + return true; + } + if (nextState.hasFocus !== this.state.hasFocus) { + return true; + } + return false; + } + + render() { + return ( + this.setState({hasFocus: true})} + onExit={() => this.setState({hasFocus: false})} + > + + + {this.props.id} + + + + + ); + } +} + +class SliderState extends React.Component { + constructor() { + super(); + this.state = {hasFocus: false}; + } + + shouldComponentUpdate(nextProps, nextState) { + if (nextProps.value !== this.props.value) { + return true; + } + if (nextState.hasFocus !== this.state.hasFocus) { + return true; + } + return false; + } + + render() { + return ( + this.setState({hasFocus: true})} + onExit={() => this.setState({hasFocus: false})} + > + + + {this.props.id} + + + + + + + ); + } +} + +class ControllerState extends React.Component { + constructor(props) { + super(props); + + this.state = { + buttons: [], + axes: [], + }; + + RCTDeviceEventEmitter.addListener('onReceivedInputEvent', e => { + if (e.type !== 'GamepadInputEvent' || this.props.controller.index !== e.gamepad) { + return; + } + if (e.eventType === 'keydown') { + const buttons = this.state.buttons.concat([]); + buttons[e.button] = true; + this.setState({buttons}); + } else if (e.eventType === 'keyup') { + const buttons = this.state.buttons.concat([]); + buttons[e.button] = false; + this.setState({buttons}); + } else if (e.eventType === 'axismove') { + const axes = this.state.axes.concat([]); + axes[e.axis] = e.value; + this.setState({axes}); + } + }); + } + + render() { + const style = { + flex: 1, + flexDirection: 'column', + marginLeft: 0.1, + marginRight: 0.1, + }; + const buttons = []; + for (let i = 0; i < this.props.controller.buttons; i++) { + buttons.push( + + ); + } + const axes = []; + for (let i = 0; i < this.props.controller.axes; i++) { + axes.push(); + } + return ( + + {buttons} + {axes} + + ); + } +} + +const ControllerList = controllers => { + if (controllers.length < 1) { + return ( + + No controllers + + ); + } + return controllers.map(c => ); +}; + +class ControllerDemo extends React.Component { + constructor() { + super(); + + this.state = { + controllers: null, + }; + + NativeModules.ControllerInfo.getControllers().then(controllers => { + this.setState({controllers: controllers}); + }); + + RCTDeviceEventEmitter.addListener('controllerConnected', e => { + console.log('controller', e); + let added = false; + const nextControllers = this.state.controllers.map(c => { + if (c.index === e.index) { + added = true; + return e; + } else { + return c; + } + }); + if (!added) { + nextControllers.push(e); + } + this.setState({controllers: nextControllers}); + }); + } + + render() { + const controllers = this.state.controllers === null + ? Waiting... + : + {ControllerList(this.state.controllers)} + ; + return ( + + + {controllers} + + ); + } +} + +const styles = StyleSheet.create({ + waiting: { + backgroundColor: '#000000', + fontSize: 0.3, + layoutOrigin: [0.5, 0.5], + paddingLeft: 0.2, + paddingRight: 0.2, + textAlign: 'center', + textAlignVertical: 'center', + transform: [{translate: [0, 0, -3]}], + }, + controllers: { + flex: 1, + flexDirection: 'row', + justifyContent: 'space-around', + transform: [{translate: [0, 0, -3]}], + }, +}); + +AppRegistry.registerComponent('ControllerDemo', () => ControllerDemo); diff --git a/Examples/Controller/static_assets/chess-world.jpg b/Examples/Controller/static_assets/chess-world.jpg new file mode 100644 index 000000000..35d02b0c2 Binary files /dev/null and b/Examples/Controller/static_assets/chess-world.jpg differ diff --git a/Examples/Controller/vr/client.js b/Examples/Controller/vr/client.js new file mode 100644 index 000000000..0a485b73a --- /dev/null +++ b/Examples/Controller/vr/client.js @@ -0,0 +1,25 @@ +// Auto-generated content. +import {VRInstance} from 'react-vr-web'; +import {MouseRayCaster} from 'ovrui'; +import * as THREE from 'three'; + +import ThreeDOFRayCaster from '../../inputs/3dof/ThreeDOFRayCaster'; + +function init(bundle, parent, options) { + const scene = new THREE.Scene(); + const vr = new VRInstance(bundle, 'ControllerDemo', parent, { + // Add custom options here + raycasters: [new ThreeDOFRayCaster(scene), new MouseRayCaster()], + cursorVisibility: 'auto', + scene: scene, + ...options, + }); + vr.render = function() { + // Any custom behavior you want to perform on each frame goes here + }; + // Begin the animation loop + vr.start(); + return vr; +} + +window.ReactVR = {init}; diff --git a/Examples/Controller/vr/index.html b/Examples/Controller/vr/index.html new file mode 100644 index 000000000..c2ab3f61d --- /dev/null +++ b/Examples/Controller/vr/index.html @@ -0,0 +1,21 @@ + + + ReactVR + + + + + + + + + diff --git a/Examples/CylindricalPanel/index.vr.js b/Examples/CylindricalPanel/index.vr.js new file mode 100644 index 000000000..a79541e76 --- /dev/null +++ b/Examples/CylindricalPanel/index.vr.js @@ -0,0 +1,83 @@ +'use strict'; + +import React from 'react'; +import {AppRegistry, asset, Pano, Text, Image, View} from 'react-vr'; +const VrButton = require('VrButton'); + +import CylindricalPanel from 'CylindricalPanel'; + +class Button extends React.Component { + constructor(props) { + super(props); + this.state = {open: false}; + } + render() { + return ( + { + this.setState({open: !this.state.open}); + }} + > + + + ); + } +} + +class CylindricalPanelDemo extends React.Component { + render() { + return ( + + + + + + Hello + + + +