├── .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 |
25 |
29 |
30 |
31 | );
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/containers/List.js:
--------------------------------------------------------------------------------
1 | import React, { Component, PropTypes } from 'react';
2 | import { connect } from 'react-redux';
3 | import DocumentMeta from 'react-document-meta';
4 | import { Items, AddItem, Button } from 'components';
5 | import { addItem, delItem } from 'modules/items';
6 | import { Entity } from 'react-aframe';
7 |
8 | const metaData = {
9 | title: 'Virtul Reality List',
10 | description: 'Start you project easy and fast with modern tools.',
11 | canonical: 'http://example.com/path/to/page',
12 | meta: {
13 | charset: 'utf-8',
14 | name: {
15 | keywords: 'react,meta,document,html,tags',
16 | },
17 | },
18 | };
19 |
20 | @connect(
21 | state => state.items,
22 | { addItem, delItem}
23 | )
24 | export class List extends Component {
25 | static propTypes = {
26 | history: PropTypes.object.isRequired,
27 | addItem: PropTypes.func.isRequired,
28 | };
29 |
30 | constructor(props) {
31 | super(props);
32 | }
33 |
34 | onClick = () => {
35 | console.log('clicked');
36 | this.props.history.push('/home');
37 | };
38 |
39 | render() {
40 | return (
41 |
42 |
43 |
44 |
45 |
46 |
47 | );
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/containers/Plain.js:
--------------------------------------------------------------------------------
1 | import React, { Component, PropTypes } from 'react';
2 | // import { Entity } from 'react-aframe';
3 | // import {Button} from 'components';
4 |
5 | export class Plain 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 |
16 | render() {
17 | return (
18 |
19 |
24 |
27 |
28 |
29 | );
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import { Provider } from 'react-redux';
4 | import { Router, Redirect, /* browserHistory, */ hashHistory } from 'react-router';
5 | import { createHashHistory } from 'history';
6 | import { syncHistoryWithStore } from 'react-router-redux';
7 | import configureStore from './store/configureStore';
8 | import routes from './routes';
9 |
10 | // load aframe and it's components
11 | require('aframe');
12 | require('aframe-text-component');
13 | const store = configureStore();
14 |
15 | const history = syncHistoryWithStore(hashHistory, store);
16 |
17 | ReactDOM.render(
18 |
19 |
20 |
21 | {routes}
22 |
23 | ,
24 | document.getElementById('root')
25 | );
26 |
--------------------------------------------------------------------------------
/src/modules/index.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux';
2 | import { routerReducer } from 'react-router-redux';
3 | import { reducer as formReducer } from 'redux-form';
4 | import { items } from './items';
5 |
6 | const rootReducer = combineReducers({
7 | form: formReducer,
8 | routing: routerReducer,
9 | items,
10 | });
11 |
12 | export default rootReducer;
13 |
--------------------------------------------------------------------------------
/src/modules/items.js:
--------------------------------------------------------------------------------
1 | function randomLoc() {
2 | return (Math.random() - 0.5) * 12;
3 | }
4 |
5 | function generatePos() {
6 | return [randomLoc(), randomLoc(), 12 + randomLoc()];
7 | }
8 |
9 | const initialState = {
10 | items: [{
11 | text: 'React',
12 | pos: generatePos(),
13 | }, {
14 | text: 'Redux',
15 | pos: generatePos(),
16 | }, {
17 | text: 'React router',
18 | pos: generatePos(),
19 | }, {
20 | text: 'Babel 6',
21 | pos: generatePos(),
22 | }, {
23 | text: 'Bootstrap webpack',
24 | pos: generatePos(),
25 | }, {
26 | text: 'Sass modules (sass-loader css-loader style-loader)',
27 | pos: generatePos(),
28 | }, {
29 | text: 'React transform',
30 | pos: generatePos(),
31 | }, {
32 | text: 'Redux logger',
33 | pos: generatePos(),
34 | }, {
35 | text: 'React document meta',
36 | pos: generatePos(),
37 | }, {
38 | text: 'Redux form',
39 | pos: generatePos(),
40 | }, {
41 | text: 'Redux simple router',
42 | pos: generatePos(),
43 | }, {
44 | text: 'Karma',
45 | pos: generatePos(),
46 | }, {
47 | text: 'Mocha',
48 | pos: generatePos(),
49 | }, {
50 | text: 'Server-side rendering',
51 | pos: generatePos(),
52 | }],
53 | };
54 |
55 | export function items(state = initialState, action) {
56 | switch (action.type) {
57 | case 'ADD_ITEM':
58 | return {
59 | ...state,
60 | items: [
61 | ...state.items, {
62 | pos: action.pos,
63 | text: action.text,
64 | },
65 | ],
66 | };
67 |
68 | case 'DELETE_ITEM':
69 | return {
70 | ...state,
71 | items: [
72 | ...state.items.slice(0, action.index),
73 | ...state.items.slice(+action.index + 1),
74 | ],
75 | };
76 |
77 | default:
78 | return state;
79 | }
80 | }
81 |
82 |
83 | export function addItem() {
84 | return {
85 | type: 'ADD_ITEM',
86 | pos: generatePos(),
87 | text: '0 0' + Math.random(),
88 | };
89 | }
90 |
91 | export function delItem(index) {
92 | return {
93 | type: 'DELETE_ITEM',
94 | index,
95 | };
96 | }
97 |
--------------------------------------------------------------------------------
/src/routes.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Route } from 'react-router';
3 |
4 | /* containers */
5 | import { App } from 'containers/App';
6 | import { Home } from 'containers/Home';
7 | import { List } from 'containers/List';
8 | import { Plain } from 'containers/Plain';
9 |
10 | export default (
11 |
12 |
13 |
14 |
15 |
16 |
17 | );
18 |
--------------------------------------------------------------------------------
/src/store/configureStore.js:
--------------------------------------------------------------------------------
1 | import { createStore, applyMiddleware } from 'redux';
2 | import thunkMiddleware from 'redux-thunk';
3 | import createLogger from 'redux-logger';
4 | import rootReducer from '../modules';
5 |
6 | const logger = createLogger({
7 | collapsed: true,
8 | predicate: () =>
9 | process.env.NODE_ENV === `development`, // eslint-disable-line no-unused-vars
10 | });
11 |
12 | const createStoreWithMiddleware = applyMiddleware(
13 | thunkMiddleware,
14 | logger
15 | )(createStore);
16 |
17 | export default function configureStore(initialState) {
18 | const store = createStoreWithMiddleware(rootReducer, initialState);
19 |
20 | if (module.hot) {
21 | // Enable Webpack hot module replacement for reducers
22 | module.hot.accept('../modules', () => {
23 | const nextRootReducer = require('../modules/index').default;
24 | store.replaceReducer(nextRootReducer);
25 | });
26 | }
27 |
28 | return store;
29 | }
30 |
--------------------------------------------------------------------------------
/src/test/reducers/items.spec.js:
--------------------------------------------------------------------------------
1 | import expect from 'expect';
2 | import { items } from 'modules/items';
3 |
4 | const initialState = {
5 | items: [],
6 | };
7 |
8 | describe('Items reducer:', () => {
9 | it('should return the initial state', () => {
10 | expect(
11 | items(initialState, {})
12 | ).toEqual(initialState);
13 | });
14 |
15 | /*
16 | it('should handle ADD', () => {
17 | const stateAfterAdd = {
18 | items: [{
19 | text: 'test'
20 | }],
21 | };
22 | const fields = { name: { value: 'test'}};
23 | expect(
24 | items(initialState, {
25 | type: 'ADD_ITEM',
26 | fields: fields,
27 | })
28 | ).toEqual(stateAfterAdd);
29 | });
30 | */
31 |
32 | it('should handle DELETE', () => {
33 | const stateWithItem = {
34 | items: [{
35 | text: 'test'
36 | }],
37 | };
38 | expect(
39 | items(stateWithItem, {
40 | type: 'DELETE_ITEM',
41 | index: 0
42 | })
43 | ).toEqual(initialState);
44 | });
45 | });
46 |
--------------------------------------------------------------------------------
/src/utils/isMobileAndTablet.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Is mobile or tablet?
3 | *
4 | * @return {Boolean}
5 | */
6 | export function isMobileAndTablet() {
7 | window.innerWidth <= 800 && window.innerHeight <= 600
8 | ? true
9 | : false;
10 | }
11 |
--------------------------------------------------------------------------------
/src/utils/parallax.js:
--------------------------------------------------------------------------------
1 | import { isMobileAndTablet } from './isMobileAndTablet';
2 |
3 | /*
4 | * Add parallax effect to element
5 | *
6 | * @param {Object} DOM element
7 | * @param {Integer} Animation speed, default: 30
8 | */
9 | export function setParallax(elem, speed = 30) {
10 | const top = (window.pageYOffset - elem.offsetTop) / speed;
11 |
12 | isMobileAndTablet
13 | ? elem.style.backgroundPosition = `0px ${ top }px`
14 | : null;
15 | }
16 |
--------------------------------------------------------------------------------
/webpack/common.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const autoprefixer = require('autoprefixer');
3 | const postcssImport = require('postcss-import');
4 | const merge = require('webpack-merge');
5 |
6 | const development = require('./dev.config.js');
7 | const production = require('./prod.config.js');
8 |
9 | require('babel-polyfill').default;
10 |
11 | const TARGET = process.env.npm_lifecycle_event;
12 |
13 | const PATHS = {
14 | app: path.join(__dirname, '../src'),
15 | build: path.join(__dirname, '../dist'),
16 | };
17 |
18 | process.env.BABEL_ENV = TARGET;
19 |
20 | const common = {
21 | entry: [
22 | PATHS.app,
23 | ],
24 |
25 | output: {
26 | path: PATHS.build,
27 | filename: 'bundle.js',
28 | },
29 |
30 | resolve: {
31 | extensions: ['', '.jsx', '.js', '.json', '.scss'],
32 | modulesDirectories: ['node_modules', PATHS.app],
33 | },
34 |
35 | module: {
36 | loaders: [{
37 | test: /\.woff(\?v=\d+\.\d+\.\d+)?$/,
38 | loader: 'url?limit=10000&mimetype=application/font-woff',
39 | }, {
40 | test: /\.woff2(\?v=\d+\.\d+\.\d+)?$/,
41 | loader: 'url?limit=10000&mimetype=application/font-woff2',
42 | }, {
43 | test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/,
44 | loader: 'url?limit=10000&mimetype=application/octet-stream',
45 | }, {
46 | test: /\.otf(\?v=\d+\.\d+\.\d+)?$/,
47 | loader: 'url?limit=10000&mimetype=application/font-otf',
48 | }, {
49 | test: /\.eot(\?v=\d+\.\d+\.\d+)?$/,
50 | loader: 'file',
51 | }, {
52 | test: /\.svg(\?v=\d+\.\d+\.\d+)?$/,
53 | loader: 'url?limit=10000&mimetype=image/svg+xml',
54 | }, {
55 | test: /\.js$/,
56 | loaders: ['babel-loader'],
57 | exclude: /node_modules/,
58 | }, {
59 | test: /\.png$/,
60 | loader: 'file?name=[name].[ext]',
61 | }, {
62 | test: /\.jpg$/,
63 | loader: 'file?name=[name].[ext]',
64 | }],
65 | },
66 |
67 | postcss: (webpack) => {
68 | return [
69 | autoprefixer({
70 | browsers: ['last 2 versions'],
71 | }),
72 | postcssImport({
73 | addDependencyTo: webpack,
74 | }),
75 | ];
76 | },
77 | };
78 |
79 | if (TARGET === 'start' || !TARGET) {
80 | module.exports = merge(development, common);
81 | }
82 |
83 | if (TARGET === 'build' || !TARGET) {
84 | module.exports = merge(production, common);
85 | }
86 |
--------------------------------------------------------------------------------
/webpack/dev.config.js:
--------------------------------------------------------------------------------
1 | const webpack = require('webpack');
2 | const ExtractTextPlugin = require('extract-text-webpack-plugin');
3 |
4 | module.exports = {
5 | devtool: 'cheap-module-eval-source-map',
6 | entry: [
7 | 'webpack-hot-middleware/client',
8 | './src/index',
9 | ],
10 | output: {
11 | publicPath: '/dist/',
12 | },
13 |
14 | module: {
15 | loaders: [
16 | {
17 | test: /\.scss$/,
18 | loader: 'style!css?localIdentName=[path][name]--[local]!postcss-loader!sass',
19 | },
20 | {
21 | test: /\.css$/,
22 | loader: 'style-loader!css-loader?-svgo',
23 | },
24 | {
25 | test: /\.json$/,
26 | loader: 'json-loader',
27 | },
28 | ],
29 | },
30 |
31 | plugins: [
32 | new webpack.DefinePlugin({
33 | 'process.env': {
34 | NODE_ENV: '"development"',
35 | },
36 | __DEVELOPMENT__: true,
37 | }),
38 | new ExtractTextPlugin('bundle.css'),
39 | new webpack.optimize.OccurenceOrderPlugin(),
40 | new webpack.HotModuleReplacementPlugin(),
41 | new webpack.NoErrorsPlugin(),
42 | new webpack.ProvidePlugin({
43 | }),
44 | ],
45 | };
46 |
--------------------------------------------------------------------------------
/webpack/prod.config.js:
--------------------------------------------------------------------------------
1 | const webpack = require('webpack');
2 | const ExtractTextPlugin = require('extract-text-webpack-plugin');
3 |
4 | module.exports = {
5 | devtool: 'source-map',
6 |
7 | entry: [
8 | ],
9 |
10 | output: {
11 | publicPath: 'dist/',
12 | },
13 |
14 | module: {
15 | loaders: [{
16 | test: /\.scss$/,
17 | loader: 'style!css!postcss-loader!sass',
18 | },
19 | {
20 | test: /\.css$/,
21 | loader: 'style-loader!css-loader?-svgo',
22 | },
23 | {
24 | test: /\.json$/,
25 | loader: 'json-loader'
26 | }],
27 | },
28 |
29 | plugins: [
30 | new webpack.DefinePlugin({
31 | 'process.env': {
32 | NODE_ENV: '"production"',
33 | },
34 | __DEVELOPMENT__: false,
35 | }),
36 | new ExtractTextPlugin('bundle.css'),
37 | new webpack.optimize.DedupePlugin(),
38 | new webpack.optimize.OccurenceOrderPlugin(),
39 | new webpack.optimize.UglifyJsPlugin({
40 | compress: {
41 | warnings: false,
42 | },
43 | }),
44 | ],
45 | };
46 |
--------------------------------------------------------------------------------