├── .babelrc ├── .eslintrc ├── .gitignore ├── README.md ├── bin └── server.js ├── index.html ├── karma.conf.js ├── package.json ├── server.js ├── src ├── components │ ├── AddItem.js │ ├── Browser.js │ ├── Button.js │ ├── Camera.js │ ├── Cursor.js │ ├── DelayText.js │ ├── Items.js │ ├── Sky.js │ └── index.js ├── containers │ ├── App.js │ ├── Home.js │ ├── List.js │ └── Plain.js ├── index.js ├── modules │ ├── index.js │ └── items.js ├── routes.js ├── store │ └── configureStore.js ├── test │ └── reducers │ │ └── items.spec.js └── utils │ ├── isMobileAndTablet.js │ └── parallax.js └── webpack ├── common.config.js ├── dev.config.js └── prod.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["react", "es2015" , "stage-0"], 3 | "plugins": [ 4 | ["transform-decorators-legacy"] 5 | ], 6 | "env": { 7 | "start": { 8 | "presets": ["react-hmre"] 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "extends": "eslint-config-airbnb", 4 | "env": { 5 | "browser": true, 6 | "node": true, 7 | "mocha": true 8 | }, 9 | "globals": { 10 | "connect": true 11 | }, 12 | "rules": { 13 | "max-len": 0, 14 | "react/jsx-uses-react": 2, 15 | "react/jsx-uses-vars": 2, 16 | "react/react-in-jsx-scope": 2, 17 | "block-scoped-var": 0, 18 | "padded-blocks": 0, 19 | "no-console": 0, 20 | "id-length": 0, 21 | "no-unused-expressions": 0, 22 | }, 23 | "plugins": [ 24 | "react" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .sass-cache/ 3 | bower_components/ 4 | app/bower_components 5 | .DS_Store 6 | .tmp 7 | ios 8 | .yo-rc.json 9 | .jshintrc 10 | .gitattributes 11 | .editorconfig 12 | npm-debug.log 13 | .gitignore 14 | dist/ 15 | .idea_modules/ 16 | /out/ 17 | atlassian-ide-plugin.xml 18 | com_crashlytics_export_strings.xml 19 | crashlytics.properties 20 | crashlytics-build.properties 21 | .idea/ 22 | 23 | #dev 24 | react-aframe/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | React Redux Aframe Boilerplate 2 | ========================= 3 | ## A modern boilerplate for developing virtual reality content on the web 4 | [This boilerplate was modified from redux-easy-boilerplate](http://anorudes.github.io/redux-easy-boilerplate/) 5 | A-Frame is a fantastic framework for building VR contents, be sure to check [the official examples](https://aframe.io/). 6 | 7 | ## Table of Contents 8 | 9 | - [About](#about) 10 | - [Installation](#installation) 11 | - [Development](#development) 12 | - [Build](#build--buildproduction) 13 | 14 | ## About 15 | - [Aframe](https://aframe.io) 16 | - [Aframe-react](https://github.com/ngokevin/aframe-react) 17 | - [Aframe-text-component](https://github.com/ngokevin/aframe-text-component) 18 | - [React](https://github.com/facebook/react) 19 | - [Redux](https://github.com/gaearon/redux) 20 | - [React Router](https://github.com/rackt/react-router) 21 | - [Babel](https://github.com/babel/babel) 22 | - [Bootstrap-loader](https://github.com/shakacode/bootstrap-loader) (configurable with .bootstraprc) 23 | - Sass modules ([sass-loader](https://github.com/jtangelder/sass-loader) [css-loader](https://github.com/webpack/css-loader) [style-loader](https://github.com/webpack/style-loader)) 24 | - [react transform](https://github.com/gaearon/react-transform) 25 | - [redux-logger](https://github.com/fcomb/redux-logger) 26 | - [react-document-meta](https://github.com/kodyl/react-document-meta) 27 | - [redux-form](https://github.com/erikras/redux-form) 28 | - [redux-simple-router](https://github.com/jlongster/redux-simple-router) 29 | - [karma](https://github.com/karma-runner/karma) 30 | - [mocha](https://github.com/mochajs/mocha) 31 | 32 | ## Installation 33 | ``` 34 | $ git clone https://github.com/HeartRunner/react-redux-aframe-boilerplate.git 35 | $ cd react-redux-aframe-boilerplate 36 | $ npm install 37 | ``` 38 | 39 | ## Development 40 | ``` 41 | $ npm start 42 | ``` 43 | Runs the project in development mode with hot-reloading of `src` folder. 44 | Open your browser at [http://localhost:3000](http://localhost:3000). 45 | 46 | ## Contribution 47 | 48 | Before push commit make sure that all modules are added in package.json 49 | 50 | ### Try 51 | ``` 52 | $ rm -rf node_modules 53 | $ npm i 54 | $ npm start 55 | ``` 56 | 57 | ## Clean 58 | ``` 59 | $ npm run clean 60 | ``` 61 | Using rimraf clean the `dist` folder, which is the target of the `build` 62 | 63 | ## Build & build:production 64 | ``` 65 | $ npm run build 66 | ``` 67 | Builds the app into the 'dist' folder for deployment 68 | ``` 69 | $ npm run build:production 70 | ``` 71 | clean the `dist` folder and rebuilds the app for deployment 72 | ### Production 73 | To run your server in production simply place the `index.html` and `dist` folder into 74 | your `web root`. 75 | 76 | In development mode the app uses `hashHistory` (e.g /#/home?_k=x928123) which 77 | keeps track of your currently location on and the state of the page. It is adviced 78 | for production to use `browserHistory` instead of `hashHistory` 79 | 80 | To make this change edit `src/index.js` 81 | 82 | the use of history push api requires that all your requests point to index.html 83 | since react-router is keeping track of the navigation (e.g this can be done with `.htaccess` file at the web root or with `nginx` configuration) 84 | 85 | ## Run karma 86 | Test is now implemented yet. 87 | ``` 88 | $ npm test 89 | ``` 90 | -------------------------------------------------------------------------------- /bin/server.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | 3 | var babelrc = fs.readFileSync('./.babelrc'); 4 | var config; 5 | 6 | try { 7 | config = JSON.parse(babelrc); 8 | } catch (err) { 9 | console.error('==> ERROR: Error parsing your .babelrc.'); 10 | console.error(err); 11 | } 12 | 13 | require('babel-core/register')(config); 14 | require('../server'); 15 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | React Redux Aframe Boilerplate 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | module.exports = (config) => { 2 | config.set({ 3 | basePath: 'src', 4 | singleRun: true, 5 | frameworks: ['mocha'], 6 | reporters: ['dots'], 7 | browsers: ['Chrome'], 8 | files: [ 9 | 'test/**/*.spec.js', 10 | ], 11 | preprocessors: { 12 | 'test/**/*.spec.js': ['webpack'], 13 | }, 14 | webpack: { 15 | resolve: { 16 | extensions: ['', '.js', '.ts'], 17 | modulesDirectories: ['node_modules', 'src'], 18 | }, 19 | module: { 20 | loaders: [{ 21 | test: /\.js$/, 22 | loader: 'babel-loader', 23 | }], 24 | }, 25 | }, 26 | webpackMiddleware: { 27 | stats: { 28 | color: true, 29 | chunkModules: false, 30 | modules: false, 31 | }, 32 | }, 33 | }); 34 | }; 35 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-redux-aframe-boilerplate", 3 | "version": "0.0.1", 4 | "description": "A modern boilerplate for building virtual reality on the web", 5 | "scripts": { 6 | "clean": "rimraf dist", 7 | "build": "webpack --progress --verbose --colors --display-error-details --config webpack/common.config.js", 8 | "build:production": "npm run clean && npm run build", 9 | "lint": "eslint src", 10 | "start": "node bin/server.js", 11 | "test": "karma start" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "https://github.com/HeartRunner/react-redux-aframe-boilerplate.git" 16 | }, 17 | "bugs": "https://github.com/HeartRunner/react-redux-aframe-boilerplate/issues", 18 | "keywords": [ 19 | "react", 20 | "reactjs", 21 | "aframe", 22 | "babel6", 23 | "boilerplate", 24 | "redux", 25 | "hot", 26 | "reload", 27 | "hmr", 28 | "live", 29 | "edit", 30 | "webpack" 31 | ], 32 | "license": "MIT", 33 | "authors": [ 34 | "Tianyu Yao (https://github.com/xiaobuu" 35 | ], 36 | "contributors": [ 37 | "Max Anoru (https://github.com/anorudes)", 38 | "Andrey Keske (https://github.com/keske)", 39 | "Ollipekka Jalonen (https://github.com/inertum)" 40 | ], 41 | "devDependencies": { 42 | "autoprefixer": "6.3.7", 43 | "babel-core": "^6.3.26", 44 | "babel-eslint": "^6.1.2", 45 | "babel-loader": "^6.2.0", 46 | "babel-plugin-react-transform": "^2.0.0", 47 | "babel-plugin-transform-decorators-legacy": "^1.3.4", 48 | "babel-polyfill": "^6.3.14", 49 | "babel-preset-es2015": "^6.3.13", 50 | "babel-preset-react": "^6.3.13", 51 | "babel-preset-stage-0": "^6.3.13", 52 | "css-loader": "^0.23.1", 53 | "eslint": "^3.1.0", 54 | "eslint-config-airbnb": "9.0.1", 55 | "eslint-plugin-react": "^5.2.2", 56 | "expect": "^1.14.0", 57 | "exports-loader": "^0.6.2", 58 | "express": "^4.13.3", 59 | "express-open-in-editor": "^1.0.0", 60 | "extract-text-webpack-plugin": "^1.0.1", 61 | "file-loader": "^0.9.0", 62 | "imports-loader": "^0.6.4", 63 | "jasmine-core": "^2.3.4", 64 | "json-loader": "^0.5.4", 65 | "karma": "^1.1.1", 66 | "karma-chrome-launcher": "^1.0.1", 67 | "karma-mocha": "^1.1.1", 68 | "karma-webpack": "^1.7.0", 69 | "less": "^2.5.3", 70 | "less-loader": "^2.2.2", 71 | "mocha": "^2.4.2", 72 | "morgan": "^1.6.1", 73 | "node-sass": "^3.1.2", 74 | "postcss-import": "^8.1.2", 75 | "postcss-loader": "^0.9.1", 76 | "react-addons-css-transition-group": "^15.2.1", 77 | "react-hot-loader": "^1.2.7", 78 | "react-loading-order-with-animation": "^1.0.0", 79 | "react-transform-hmr": "^1.0.1", 80 | "redux-logger": "2.6.1", 81 | "resolve-url-loader": "^1.4.3", 82 | "rimraf": "^2.5.0", 83 | "sass-loader": "^4.0.0", 84 | "style-loader": "^0.13.0", 85 | "url-loader": "^0.5.6", 86 | "webpack": "^1.9.6", 87 | "webpack-dev-middleware": "^1.2.0", 88 | "webpack-dev-server": "^1.8.2", 89 | "webpack-hot-middleware": "^2.4.1", 90 | "webpack-merge": "^0.14.0" 91 | }, 92 | "dependencies": { 93 | "aframe": "git://github.com/aframevr/aframe.git", 94 | "aframe-text-component": "^0.3.0", 95 | "babel-preset-react-hmre": "^1.0.1", 96 | "classnames": "^2.1.3", 97 | "history": "^3.0.0", 98 | "react": "^15.2.1", 99 | "react-aframe": "git+https://github.com/xiaobuu/react-aframe.git", 100 | "react-document-meta": "^2.0.0-rc2", 101 | "react-dom": "^15.2.1", 102 | "react-redux": "^4.2.0", 103 | "react-router": "^2.0.0-rc4", 104 | "react-router-redux": "^4.0.5", 105 | "redux": "^3.1.7", 106 | "redux-form": "^5.3.1", 107 | "redux-thunk": "^2.1.0" 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | const http = require('http'); 2 | const express = require('express'); 3 | const app = express(); 4 | 5 | app.use(require('morgan')('short')); 6 | 7 | (function initWebpack() { 8 | const webpack = require('webpack'); 9 | const webpackConfig = require('./webpack/common.config'); 10 | const compiler = webpack(webpackConfig); 11 | 12 | app.use(require('webpack-dev-middleware')(compiler, { 13 | noInfo: true, publicPath: webpackConfig.output.publicPath, 14 | })); 15 | 16 | app.use(require('webpack-hot-middleware')(compiler, { 17 | log: console.log, path: '/__webpack_hmr', heartbeat: 10 * 1000, 18 | })); 19 | 20 | app.use(express.static(__dirname + '/')); 21 | })(); 22 | 23 | app.get(/.*/, function root(req, res) { 24 | res.sendFile(__dirname + '/index.html'); 25 | }); 26 | 27 | const server = http.createServer(app); 28 | server.listen(process.env.PORT || 3000, function onListen() { 29 | const address = server.address(); 30 | console.log('Listening on: %j', address); 31 | console.log(' -> that probably means: http://localhost:%d', address.port); 32 | }); 33 | -------------------------------------------------------------------------------- /src/components/AddItem.js: -------------------------------------------------------------------------------- 1 | import { Entity } from 'react-aframe'; 2 | import React from 'react'; 3 | 4 | export default props => ( 5 | 6 | 9 | 10 | ); 11 | -------------------------------------------------------------------------------- /src/components/Browser.js: -------------------------------------------------------------------------------- 1 | import { 2 | Entity, 3 | Animation, 4 | } from 'react-aframe'; 5 | import React, { PropTypes, Component } from 'react'; 6 | import { connect } from 'react-redux'; 7 | import { hashHistory } from 'react-router'; 8 | 9 | @connect( 10 | (state) => ({location: state.routing.locationBeforeTransitions ? state.routing.locationBeforeTransitions.pathname : 'First Page!'}) 11 | ) 12 | export default class Browser extends Component { 13 | 14 | 15 | onBackPress = () => { 16 | console.log('clicked back'); 17 | hashHistory.goBack(); 18 | }; 19 | 20 | render() { 21 | const {props} = this; 22 | console.log(props.location); 23 | return ( 24 | 34 | 43 | 54 | 55 | 56 | 57 | ); 58 | } 59 | } 60 | 61 | Browser.propTypes = { 62 | location: PropTypes.string.isRequired, 63 | }; -------------------------------------------------------------------------------- /src/components/Button.js: -------------------------------------------------------------------------------- 1 | import { Entity, Animation } from 'react-aframe'; 2 | import React, { PropTypes } from 'react'; 3 | 4 | import * as Aframe from 'react-aframe'; 5 | console.log(Aframe); 6 | const Button = props => ( 7 | 17 | 18 | 19 | ); 20 | 21 | Button.propTypes = { 22 | onClick: PropTypes.func, 23 | }; 24 | 25 | export default Button; 26 | -------------------------------------------------------------------------------- /src/components/Camera.js: -------------------------------------------------------------------------------- 1 | import { Entity } from 'react-aframe'; 2 | import React from 'react'; 3 | 4 | export default props => ( 5 | 6 | 7 | 8 | ); 9 | -------------------------------------------------------------------------------- /src/components/Cursor.js: -------------------------------------------------------------------------------- 1 | import { 2 | Entity, 3 | Animation, 4 | } from 'react-aframe'; 5 | import React from 'react'; 6 | 7 | export default props => { 8 | const geometry = { 9 | primitive: 'ring', 10 | radiusInner: 0.07, 11 | radiusOuter: 0.13, 12 | }; 13 | const material = { 14 | color: props.color || '#fced2c', 15 | shader: 'flat', 16 | // opacity: props.opacity || 0.9, 17 | }; 18 | return ( 19 | 29 | 38 | 47 | 48 | 49 | ); 50 | }; 51 | -------------------------------------------------------------------------------- /src/components/DelayText.js: -------------------------------------------------------------------------------- 1 | import { Entity } from 'react-aframe'; 2 | import React, { Component, PropTypes } from 'react'; 3 | 4 | 5 | export default class DelayText extends Component { 6 | static propTypes = { 7 | text: PropTypes.string, 8 | delay: PropTypes.number, 9 | }; 10 | 11 | state = { 12 | canRender: false, 13 | }; 14 | 15 | componentDidMount() { 16 | this.handle = setTimeout(() => { 17 | this.handle = null; 18 | this.setState({ canRender: true }); 19 | }, this.props.delay || 100); 20 | } 21 | 22 | ComponentWillUnmount() { 23 | if (this.handle) clearTimeout(this.handle); 24 | } 25 | 26 | render() { 27 | const { text, ...rest } = this.props; 28 | return this.state.canRender ? : ; 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/components/Items.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import { Entity } from 'react-aframe'; 3 | import { DelayText } from 'components'; 4 | 5 | export default class Items extends Component { 6 | 7 | static propTypes = { 8 | items: PropTypes.array, 9 | delItem: PropTypes.func, 10 | }; 11 | 12 | constructor(props) { 13 | super(props); 14 | } 15 | 16 | onClick = (index) => { 17 | return (event) => { 18 | event.preventDefault(); 19 | console.log('click', index); 20 | this.props.delItem(index); 21 | }; 22 | }; 23 | 24 | 25 | // move up the label a little 26 | changeLoc(originLocs) { 27 | const locs = originLocs.slice(); 28 | locs[1] = locs[1] + 1; 29 | locs[0] = locs[0] - 1; 30 | return locs.join(' '); 31 | } 32 | 33 | render() { 34 | const { items } = this.props; 35 | return ( 36 | 37 | { 38 | items.map((item, index) => 39 | 40 | 50 | { 51 | // aframe-text-component is extremely slow, so I delay the rendering of each text 52 | } 53 | 54 | 55 | ) 56 | } 57 | 58 | ); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/components/Sky.js: -------------------------------------------------------------------------------- 1 | import { Entity } from 'react-aframe'; 2 | import React from 'react'; 3 | 4 | export default () => ( 5 | 8 | ); 9 | -------------------------------------------------------------------------------- /src/components/index.js: -------------------------------------------------------------------------------- 1 | export Cursor from './Cursor'; 2 | export Camera from './Camera'; 3 | export Items from './Items'; 4 | export AddItem from './AddItem'; 5 | export DelayText from './DelayText'; 6 | export Sky from './Sky'; 7 | export Button from './Button'; 8 | export Browser from './Browser'; 9 | -------------------------------------------------------------------------------- /src/containers/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Scene } from 'react-aframe'; 3 | import DocumentMeta from 'react-document-meta'; 4 | 5 | import { Cursor, Camera, Sky, Browser } from 'components'; 6 | 7 | const metaData = { 8 | title: 'Virtual Reality', 9 | description: 'react-redux-aframe-boilerplate', 10 | canonical: 'https://github.com/HeartRunner/react-redux-aframe-boilerplate', 11 | meta: { 12 | charset: 'utf-8', 13 | name: { 14 | keywords: 'react,meta,aframe,virtual,reality', 15 | }, 16 | }, 17 | }; 18 | export class App extends Component { 19 | static propTypes = { 20 | children: React.PropTypes.any, 21 | }; 22 | 23 | render() { 24 | return ( 25 |
26 | 27 | 28 | 29 | 30 | 31 | {this.props.children} 32 | 33 |
34 | ); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/containers/Home.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import { Entity } from 'react-aframe'; 3 | import {Button} from 'components'; 4 | 5 | export class Home extends Component { 6 | static propTypes = { 7 | history: PropTypes.object.isRequired, 8 | route: PropTypes.object.isRequired, 9 | }; 10 | 11 | onClick = () => { 12 | console.log('clicked!!'); 13 | this.props.history.push('/list'); 14 | }; 15 | onClickPlain = () => { 16 | this.props.history.push('/plain'); 17 | }; 18 | render() { 19 | return ( 20 | 21 |