├── .babelrc
├── .editorconfig
├── .eslintrc
├── .github
└── FUNDING.yml
├── .gitignore
├── .npmrc
├── CHANGELOG.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── docs
└── flowchart.png
├── mock-server
└── api
│ └── newsByPage.json
├── package.json
├── scripts
├── build.js
├── build
│ └── index.js
├── config.js
├── dev.js
├── dev
│ └── index.js
├── gt.js
├── gt
│ └── index.js
└── webpack.config.js
├── src
├── api
│ └── news.js
├── components
│ ├── DemoButton.js
│ ├── Logo.js
│ └── Toast.js
├── config
│ ├── actionTypes.js
│ ├── cookie.js
│ ├── entries.js
│ ├── environments.js
│ ├── errors.js
│ ├── requestMethod.js
│ ├── routes.js
│ ├── storage.js
│ └── toast.js
├── containers
│ ├── Demo
│ │ └── PianistDemo.js
│ ├── Index
│ │ └── Home.js
│ └── common
│ │ ├── Base.js
│ │ ├── Base.less
│ │ ├── NoMatch.js
│ │ └── Toast.js
├── entries
│ ├── about.js
│ └── index.js
├── errors
│ ├── FetchError.js
│ ├── ServerError.js
│ ├── TimeoutError.js
│ └── index.js
├── html
│ └── index.html
├── i18n
│ ├── index.js
│ └── zh-cn.js
├── images
│ └── vivaxy.20150726.jpg
├── lib
│ ├── checkLogin.js
│ ├── environment.js
│ ├── fetch.js
│ ├── getURLParameter.js
│ ├── logout.js
│ ├── processErrors.js
│ ├── render.js
│ ├── requestPath.js
│ ├── setTitle.js
│ ├── sleep.js
│ ├── store.js
│ ├── style.js
│ └── style.less
├── pages
│ ├── about.js
│ └── index.js
├── redux
│ ├── button.js
│ ├── newsList.js
│ ├── pagination.js
│ ├── reducers.js
│ ├── routing.js
│ └── toast.js
└── routes
│ ├── about.js
│ └── index.js
├── test
└── get-base-dirname.js
└── yarn.lock
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | [
4 | "latest",
5 | {
6 | "modules": false
7 | }
8 | ],
9 | "react",
10 | "stage-0"
11 | ],
12 | "plugins": [
13 | "transform-runtime"
14 | ]
15 | }
16 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_style = space
5 | indent_size = 4
6 | end_of_line = lf
7 | charset = utf-8
8 | trim_trailing_whitespace = true
9 | insert_final_newline = true
10 | curly_bracket_next_line = false
11 | spaces_around_operators = true
12 | indent_brace_style = 1tbs
13 |
14 | [*.js]
15 | quote_type = single
16 |
17 | [*.{html,less,css,json}]
18 | quote_type = double
19 |
20 | [package.json]
21 | indent_size = 2
22 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "vivaxy"
3 | }
4 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | patreon: vivaxy
2 | open_collective: vivaxy_personal
3 | custom: ['https://gist.github.com/vivaxy/58eed1803a2eddda05c90aed99430de2']
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | .DS_Store
3 | node_modules
4 | npm-debug.log
5 | release
6 | yarn-error.log
7 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | registry=https://registry.npmjs.org/
2 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Change Log
2 |
3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
4 |
5 |
6 | # [1.8.0](https://github.com/vivaxy/gt-react-scaffold/compare/v1.7.0...v1.8.0) (2017-05-04)
7 |
8 |
9 | ### Features
10 |
11 | * **dependencies:** :arrow_up:Move react react-dom redux redux-thunk into dependencies ([20b4304](https://github.com/vivaxy/gt-react-scaffold/commit/20b4304))
12 | * **report:** :sparkles:Replace `webpack-visualizer-plugin` with `webpack-bundle-analyzer` ([8057b69](https://github.com/vivaxy/gt-react-scaffold/commit/8057b69))
13 |
14 |
15 |
16 |
17 | # [1.7.0](https://github.com/vivaxy/gt-react-scaffold/compare/v1.6.1...v1.7.0) (2017-05-03)
18 |
19 |
20 | ### Features
21 |
22 | * **babel:** :sparkles:Using babel-runtime instead of babel-polyfill, reduce the release files' size ([f2b729a](https://github.com/vivaxy/gt-react-scaffold/commit/f2b729a))
23 |
24 |
25 |
26 |
27 | ## [1.6.1](https://github.com/vivaxy/gt-react-scaffold/compare/v1.6.0...v1.6.1) (2017-05-03)
28 |
29 |
30 |
31 |
32 | # [1.6.0](https://github.com/vivaxy/gt-react-scaffold/compare/v1.5.2...v1.6.0) (2017-04-18)
33 |
34 |
35 | ### Features
36 |
37 | * **dep:** :alien:Upgrade react to 15.5, and react-dom to 15.5 ([230e3e1](https://github.com/vivaxy/gt-react-scaffold/commit/230e3e1))
38 | * **eslint:** :sparkles:Add eslint-loader, Upgrade react-pianist and react-router because of prop-ty ([7d695c3](https://github.com/vivaxy/gt-react-scaffold/commit/7d695c3))
39 | * **openBrowser:** :sparkles:Optimize open browser ([cb55563](https://github.com/vivaxy/gt-react-scaffold/commit/cb55563))
40 |
41 |
42 |
43 |
44 | ## [1.5.2](https://github.com/vivaxy/gt-react-scaffold/compare/v1.5.1...v1.5.2) (2017-04-10)
45 |
46 |
47 |
48 |
49 | ## [1.5.1](https://github.com/vivaxy/gt-react-scaffold/compare/v1.5.0...v1.5.1) (2017-04-01)
50 |
51 |
52 | ### Bug Fixes
53 |
54 | * **gt:** :bug:Fix gt after message; Add yarn install in gt; Update webpack-dev-server to 2.x in pack ([8849562](https://github.com/vivaxy/gt-react-scaffold/commit/8849562))
55 | * **gt:** :bug:Fix gt.js ([e84665d](https://github.com/vivaxy/gt-react-scaffold/commit/e84665d))
56 |
57 |
58 |
59 |
60 | # [1.5.0](https://github.com/vivaxy/gt-react-scaffold/compare/v1.4.0...v1.5.0) (2017-03-04)
61 |
62 |
63 | ### Features
64 |
65 | * **test:** :sparkles:Add ava test ([a5bf82f](https://github.com/vivaxy/gt-react-scaffold/commit/a5bf82f))
66 |
67 |
68 |
69 |
70 | # [1.4.0](https://github.com/vivaxy/gt-react-scaffold/compare/v1.3.0...v1.4.0) (2017-03-04)
71 |
72 |
73 | ### Bug Fixes
74 |
75 | * :bug:Fix yarn.lock resolved urls; Add missing dependency: redux; ([229ac48](https://github.com/vivaxy/gt-react-scaffold/commit/229ac48))
76 | * **gt:** :bug:Fix copy `.npmrc` ([baf9a1a](https://github.com/vivaxy/gt-react-scaffold/commit/baf9a1a))
77 | * **gt:** :bug:Fix template, render file project name ([9b72633](https://github.com/vivaxy/gt-react-scaffold/commit/9b72633))
78 | * **gt.js:** :bug:Fix gt copy file ([5c24c19](https://github.com/vivaxy/gt-react-scaffold/commit/5c24c19))
79 | * **gt.js:** :bug:Update gt.js ([565f05d](https://github.com/vivaxy/gt-react-scaffold/commit/565f05d))
80 | * **scripts:** :bug:Fix release script ([04ece5d](https://github.com/vivaxy/gt-react-scaffold/commit/04ece5d))
81 |
82 |
83 | ### Features
84 |
85 | * **babel:** :sparkles:Update babel-preset-es2015 babel-preset-latest ([5545282](https://github.com/vivaxy/gt-react-scaffold/commit/5545282))
86 | * **eslint:** :sparkles:Add eslint, rules based on airbnb/javascript ([2b976e2](https://github.com/vivaxy/gt-react-scaffold/commit/2b976e2))
87 | * **npmrc:** :art:Add .npmrc to maintain npm registry ([5dac42b](https://github.com/vivaxy/gt-react-scaffold/commit/5dac42b))
88 |
89 |
90 |
91 |
92 | # [1.3.0](https://github.com/vivaxy/gt-react-scaffold/compare/v1.2.0...v1.3.0) (2017-01-26)
93 |
94 |
95 | ### Bug Fixes
96 |
97 | * **gt.js:** :bug:Fix gt info in README.md ([00d772b](https://github.com/vivaxy/gt-react-scaffold/commit/00d772b))
98 |
99 |
100 | ### Features
101 |
102 | * **postcss:** :sparkles:Add postcss ([df5d83a](https://github.com/vivaxy/gt-react-scaffold/commit/df5d83a))
103 | * **rename:** :sparkles:Rename `react-scaffold` to `gt-react-scaffold` ([5107d7b](https://github.com/vivaxy/gt-react-scaffold/commit/5107d7b))
104 | * **webpack:** :sparkles:Use `scripts/dev.js` to start webpack dev server, for more controllable. No ([f6b7103](https://github.com/vivaxy/gt-react-scaffold/commit/f6b7103))
105 |
106 |
107 |
108 |
109 | # [1.2.0](https://github.com/vivaxy/gt-react-scaffold/compare/v1.1.0...v1.2.0) (2017-01-09)
110 |
111 |
112 | ### Bug Fixes
113 |
114 | * **gt:** :bug:fix source folder name ([23590ca](https://github.com/vivaxy/gt-react-scaffold/commit/23590ca))
115 | * **yarn:** :arrow_up: ([e314496](https://github.com/vivaxy/gt-react-scaffold/commit/e314496))
116 |
117 |
118 | ### Features
119 |
120 | * **fetch:** :art:remove fetch cache ([490b408](https://github.com/vivaxy/gt-react-scaffold/commit/490b408))
121 | * **fetch:** :sparkles:add timeout for fetch ([8a0cddb](https://github.com/vivaxy/gt-react-scaffold/commit/8a0cddb))
122 | * :sparkles:improve page transition style ([3552fc6](https://github.com/vivaxy/gt-react-scaffold/commit/3552fc6))
123 | * **gt:** :sparkles:add sleep in gt.js ([e15b49c](https://github.com/vivaxy/gt-react-scaffold/commit/e15b49c))
124 | * **page transition:** :sparkles: ([0643b9e](https://github.com/vivaxy/gt-react-scaffold/commit/0643b9e))
125 |
126 |
127 |
128 |
129 | # [1.1.0](https://github.com/vivaxy/gt-react-scaffold/compare/v1.0.2...v1.1.0) (2016-11-27)
130 |
131 |
132 | ### Bug Fixes
133 |
134 | * **fetch:** :bug:fix cached response cannot more than once ([7883557](https://github.com/vivaxy/gt-react-scaffold/commit/7883557))
135 | * **webpack:** :bug:only open browser when dev server ([9236361](https://github.com/vivaxy/gt-react-scaffold/commit/9236361))
136 |
137 |
138 | ### Features
139 |
140 | * **fetch:** :art:support CORS ([1ce3a82](https://github.com/vivaxy/gt-react-scaffold/commit/1ce3a82))
141 | * **webpack:** :sparkles:added OccurrenceOrderPlugin ([e003181](https://github.com/vivaxy/gt-react-scaffold/commit/e003181))
142 | * **webpack:** :sparkles:after npm start, open the browser ([57b43c9](https://github.com/vivaxy/gt-react-scaffold/commit/57b43c9))
143 |
144 |
145 |
146 |
147 | ## 1.0.2 (2016-11-24)
148 |
149 |
150 | ### Bug Fixes
151 |
152 | * **fetch:** :bug:fix cached fetch ([61817e2](https://github.com/vivaxy/gt-react-scaffold/commit/61817e2))
153 |
154 |
155 |
156 | ## 1.0.1
157 |
158 | * optimize `contianers`
159 |
160 | ## 1.0.0
161 |
162 | * first release
163 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | Feel free to submit any issue.
2 |
3 | Steps to make contributions:
4 |
5 | - Update codes.
6 | - Submit your changes by [`gacp`](https://github.com/vivaxy/gacp).
7 | - Run `yarn run release` to update `CHANGELOG.md` and bump version.
8 | - Run [`gacp`](https://github.com/vivaxy/gacp) to push changes into origin.
9 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2016 vivaxy
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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## Initializing
2 |
3 | - Install nodejs.
4 | - Install [gt](https://github.com/vivaxy/granturismo).
5 |
6 | `yarn global add granturismo`
7 |
8 | - Add scaffold to gt `gt config add vivaxy/gt-react-scaffold https://github.com/vivaxy/gt-react-scaffold.git`.
9 | - Create your project directory `mkdir my-project-name && cd my-project-name` or `git clone ...`.
10 | - Run `gt init`.
11 | - Select `vivaxy/gt-react-scaffold`.
12 |
13 | ## Contributing
14 |
15 | [Contributing](CONTRIBUTING.md)
16 |
17 | ----------
18 |
19 | # gt-react-scaffold
20 |
21 | ## Feature
22 |
23 | - react
24 | - redux
25 | - webpack
26 | - eslint
27 | - page navigation animation
28 | - react router
29 | - react redux
30 | - redux thunk
31 | - react hot reload
32 | - webpack development server
33 | - multiple entries
34 | - mock server
35 | - unified error center
36 | - internationalization
37 | - environments
38 | - webpack visualizer
39 | - postcss
40 | - autoprefixer
41 | - less
42 | - test cases
43 |
44 | ## Concept
45 |
46 | 
47 |
48 | As we find out html are mostly same in react projects, we extract the same html into a template file which lays in `html` folder.
49 |
50 | `html-webpack-plugin` is used to generate released html files, which load corresponding js files.
51 |
52 | `entries` stores js main methods, we split main container to `containers` because entry could not be hot-module-replaced.
53 |
54 | `entries`s import `containers`s, which is one to one correspondent. `render` method in entry provides all commonly used setups, such as redux store, provider, injectTapEventPlugin for material-ui, and custom styles.
55 |
56 | `containers` is the headquarters which imports all dummy components, passing through props to them. Also, `containers` interacts with redux `reducers` and `actions`.
57 |
58 | It is recommended that you wrap containers into another. `redux` will re-render a container when and only when mapped reducers have been updated.
59 |
60 | We do not use async redux for ajax requests because ajax status is not that important for us to record. So, we invoke `api` in `containers`, get response, and determine what `actions` to call.
61 |
62 | We store every string in `i18n` for better internationalization.
63 |
64 | Built project files will be in the `release` folder. With `build.log`, you can see every build details. With `stats.html`, you can optimize your dependencies to make project smaller.
65 |
66 | ## Developing
67 |
68 | - `yarn run dev`
69 |
70 | ## Building
71 |
72 | - `yarn run build`
73 |
74 | ## Reference
75 |
76 | - https://github.com/webpack/docs/wiki/webpack-dev-server
77 | - https://github.com/gaearon/react-hot-loader/tree/master/docs
78 | - https://github.com/reactjs/react-router-redux
79 | - https://github.com/ampedandwired/html-webpack-plugin
80 | - https://github.com/ReactTraining/react-router
81 |
--------------------------------------------------------------------------------
/docs/flowchart.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vivaxy/gt-react-scaffold/086082ebcb41401639706c23cae929e07ef046ae/docs/flowchart.png
--------------------------------------------------------------------------------
/mock-server/api/newsByPage.json:
--------------------------------------------------------------------------------
1 | {
2 | "code": 0,
3 | "message": "some error from server",
4 | "data": {
5 | "list": [
6 | {
7 | "id": 1,
8 | "name": "this is a piece of news"
9 | }
10 | ]
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "gt-react-scaffold",
3 | "version": "1.8.0",
4 | "description": "react gt scaffold",
5 | "main": "webpack.config.js",
6 | "scripts": {
7 | "test": "ava",
8 | "lint": "eslint ./src",
9 | "release": "standard-version && git push --follow-tag",
10 | "dev": "NODE_ENV=development node ./scripts/dev.js",
11 | "beta": "NODE_ENV=beta npm run build",
12 | "production": "NODE_ENV=production npm run build",
13 | "build": "npm run build:clean && npm run build:webpack",
14 | "build:clean": "rm -rf ./release && mkdir ./release",
15 | "build:webpack": "node ./scripts/build.js"
16 | },
17 | "repository": {
18 | "type": "git",
19 | "url": "git+https://github.com/vivaxy/gt-react-scaffold.git"
20 | },
21 | "keywords": [
22 | "react",
23 | "gt-react-scaffold",
24 | "scaffold",
25 | "gt"
26 | ],
27 | "author": "vivaxy",
28 | "license": "MIT",
29 | "bugs": {
30 | "url": "https://github.com/vivaxy/gt-react-scaffold/issues"
31 | },
32 | "homepage": "https://github.com/vivaxy/gt-react-scaffold#readme",
33 | "devDependencies": {
34 | "autoprefixer": "^6.7.0",
35 | "ava": "^0.18.2",
36 | "babel-core": "^6.22.1",
37 | "babel-loader": "^7.0.0",
38 | "babel-preset-latest": "^6.22.0",
39 | "babel-preset-react": "^6.11.1",
40 | "babel-preset-stage-0": "^6.22.0",
41 | "babel-register": "^6.24.0",
42 | "css-loader": "^0.24.0",
43 | "eslint": "^3.18.0",
44 | "eslint-config-vivaxy": "^0.1.1",
45 | "eslint-loader": "^1.7.1",
46 | "execa": "^0.6.0",
47 | "file-loader": "^0.9.0",
48 | "glob": "^7.1.1",
49 | "html-webpack-plugin": "^2.28.0",
50 | "ip": "^1.1.4",
51 | "isomorphic-fetch": "^2.2.1",
52 | "less": "^2.7.1",
53 | "less-loader": "^2.2.3",
54 | "listr": "^0.8.0",
55 | "log-update": "^1.0.2",
56 | "numeral": "^2.0.4",
57 | "postcss-loader": "^1.2.2",
58 | "prop-types": "^15.5.7",
59 | "react-addons-css-transition-group": "^15.3.2",
60 | "react-dev-utils": "^0.5.2",
61 | "react-hot-loader": "next",
62 | "react-pianist": "^0.0.12",
63 | "react-redux": "^5.0.4",
64 | "react-router": "3",
65 | "react-router-redux": "^4.0.6",
66 | "redux-devtools": "^3.3.1",
67 | "standard-version": "^3.0.0",
68 | "style-loader": "^0.13.1",
69 | "tiny-cookie": "^0.5.5",
70 | "url-loader": "^0.5.7",
71 | "webpack": "^2.2.1",
72 | "webpack-bundle-analyzer": "^2.4.0",
73 | "webpack-dev-server": "^2.4.1"
74 | },
75 | "browserslist": [
76 | "> 1%",
77 | "ie >= 9"
78 | ],
79 | "ava": {
80 | "files": [
81 | "test/*.js"
82 | ],
83 | "source": [
84 | "src/**/*.js"
85 | ],
86 | "require": [
87 | "babel-register"
88 | ],
89 | "babel": "inherit",
90 | "verbose": true
91 | },
92 | "dependencies": {
93 | "babel-plugin-transform-runtime": "^6.23.0",
94 | "babel-polyfill": "^6.23.0",
95 | "babel-runtime": "^6.23.0",
96 | "react": "^15.5.4",
97 | "react-dom": "^15.5.4",
98 | "redux": "^3.6.0",
99 | "redux-create-reducer": "^1.1.1",
100 | "redux-thunk": "^2.2.0"
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/scripts/build.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @since 2017-04-01 16:33:06
3 | * @author vivaxy
4 | */
5 |
6 | require('babel-polyfill');
7 | require('babel-register');
8 | require('./build/index');
9 |
--------------------------------------------------------------------------------
/scripts/build/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @since 2017-04-01 16:33:18
3 | * @author vivaxy
4 | */
5 |
6 | /* eslint-disable no-console */
7 | import webpack from 'webpack';
8 | import webpackConfig from '../webpack.config';
9 |
10 | const compiler = webpack(webpackConfig);
11 |
12 | compiler.run((err) => {
13 | if (err) {
14 | console.error(err);
15 | }
16 | });
17 |
--------------------------------------------------------------------------------
/scripts/config.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @since 2017-01-31 17:19
3 | * @author vivaxy
4 | */
5 |
6 | const ip = require('ip');
7 |
8 | exports.DEVELOPMENT_IP = ip.address();
9 | exports.DEVELOPMENT_PORT = Math.floor(Math.random() * 65536);
10 | exports.RELEASE_PATH = 'release';
11 | exports.MOCK_SERVER_BASE = 'mock-server';
12 |
--------------------------------------------------------------------------------
/scripts/dev.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @since 2017-01-12 17:35
3 | * @author vivaxy
4 | */
5 |
6 | require('babel-polyfill');
7 | require('babel-register');
8 | require('./dev/index');
9 |
--------------------------------------------------------------------------------
/scripts/dev/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @since 2017-04-01 16:30:03
3 | * @author vivaxy
4 | */
5 |
6 | /* eslint-disable no-console */
7 | import open from 'react-dev-utils/openBrowser';
8 | import webpack from 'webpack';
9 | import WebpackDevServer from 'webpack-dev-server';
10 |
11 | import webpackConfig from '../webpack.config';
12 | import config from '../config';
13 |
14 | const compiler = webpack(webpackConfig);
15 |
16 | let opened = false;
17 |
18 | const devServerOptions = {
19 | contentBase: [
20 | config.RELEASE_PATH,
21 | config.MOCK_SERVER_BASE,
22 | ],
23 | hot: true,
24 | historyApiFallback: true,
25 | stats: {
26 | colors: true,
27 | },
28 | };
29 |
30 | const server = new WebpackDevServer(compiler, devServerOptions);
31 |
32 | const openBrowser = () => {
33 | const address = server.listeningApp.address();
34 | const url = `http://${address.address}:${address.port}`;
35 | console.log(` server started: ${url}`);
36 | open(`${url}/html/index.html`);
37 | };
38 |
39 | compiler.plugin('done', () => {
40 | if (!opened) {
41 | opened = true;
42 | openBrowser();
43 | }
44 | });
45 |
46 | server.listen(config.DEVELOPMENT_PORT, config.DEVELOPMENT_IP, (err) => {
47 | if (err) {
48 | console.log(err);
49 | }
50 | });
51 |
52 | const stdIn = process.stdin;
53 | stdIn.setEncoding('utf8');
54 | stdIn.on('data', openBrowser);
55 |
--------------------------------------------------------------------------------
/scripts/gt.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @since 2016-11-16 15:37
3 | * @author vivaxy
4 | */
5 |
6 | if (!global._babelPolyfill) { // eslint-disable-line no-underscore-dangle
7 | require('babel-polyfill'); // eslint-disable-line global-require
8 | }
9 | require('babel-register');
10 | module.exports = require('./gt/index');
11 |
--------------------------------------------------------------------------------
/scripts/gt/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @since 2017-03-21 15:55:59
3 | * @author vivaxy
4 | */
5 |
6 | import Listr from 'listr';
7 | import execa from 'execa';
8 |
9 | const sleep = (timeout) => {
10 | return new Promise((resolve) => {
11 | setTimeout(resolve, timeout);
12 | });
13 | };
14 |
15 | const copyFiles = (options) => {
16 | const { presets } = options;
17 | const files = [
18 | 'docs',
19 | 'mock-server',
20 | 'scripts',
21 | 'src',
22 | '.babelrc',
23 | '.editorconfig',
24 | '.eslintrc',
25 | '.gitignore',
26 | '.npmrc',
27 | 'LICENSE',
28 | ];
29 | return async() => {
30 | await sleep(500);
31 | await presets.copyFiles(files);
32 | };
33 | };
34 |
35 | const updatePackageJSON = (options) => {
36 | const { project, presets } = options;
37 | const projectGit = project.git || {};
38 | const filename = 'package.json';
39 |
40 | return async() => {
41 | await sleep(500);
42 | await presets.updateJson(filename, (data) => {
43 | const {
44 | version,
45 | description,
46 | main,
47 | scripts,
48 | repository,
49 | keywords,
50 | author,
51 | license,
52 | bugs,
53 | dependencies,
54 | devDependencies,
55 | peerDependencies,
56 | } = data;
57 |
58 | return {
59 | name: project.name,
60 | version: '0.0.0',
61 | gtScaffoldVersion: version,
62 | description,
63 | main,
64 | scripts,
65 | repository: {
66 | ...repository,
67 | url: projectGit.repositoryURL,
68 | },
69 | keywords,
70 | author: projectGit.username,
71 | license,
72 | bugs: {
73 | ...bugs,
74 | url: undefined,
75 | },
76 | dependencies,
77 | devDependencies,
78 | peerDependencies,
79 | };
80 | });
81 | };
82 | };
83 |
84 | const updateREADME = (options) => {
85 | const { project, presets } = options;
86 | const filename = 'README.md';
87 |
88 | return async() => {
89 | await sleep(500);
90 | await presets.updateFile(filename, (content) => {
91 | const projectData = content.split('----------\n\n')[1];
92 | return projectData.replace(/gt-react-scaffold/g, `${project.name}
93 |
94 | Initialized by [vivaxy/gt-react-scaffold](https://github.com/vivaxy/gt-react-scaffold)`);
95 | });
96 | };
97 | };
98 |
99 | const updateTemplate = (options) => {
100 | const { project, presets } = options;
101 | const filename = 'src/html/index.html';
102 |
103 | return async() => {
104 | await sleep(1000);
105 | await presets.updateFile(filename, (content) => {
106 | return content.replace(/gt-react-scaffold/g, project.name);
107 | });
108 | };
109 | };
110 |
111 | const updateRender = (options) => {
112 | const { project, presets } = options;
113 | const filename = 'src/lib/render.js';
114 |
115 | return async() => {
116 | await sleep(1000);
117 | await presets.updateFile(filename, (content) => {
118 | return content.replace(/gt-react-scaffold/g, project.name);
119 | });
120 | };
121 | };
122 |
123 | const yarnInstall = () => {
124 | return async() => {
125 | await execa('yarn', ['install']);
126 | };
127 | };
128 |
129 | export const init = (options) => {
130 | return new Listr([
131 | {
132 | title: 'copy files',
133 | task: copyFiles(options),
134 | },
135 | {
136 | title: 'update template',
137 | task: updateTemplate(options),
138 | },
139 | {
140 | title: 'update render',
141 | task: updateRender(options),
142 | },
143 | {
144 | title: 'update package.json',
145 | task: updatePackageJSON(options),
146 | },
147 | {
148 | title: 'update README.md',
149 | task: updateREADME(options),
150 | },
151 | {
152 | title: 'run yarn install',
153 | task: yarnInstall(options),
154 | },
155 | ]);
156 | };
157 |
158 | /* eslint-disable no-console */
159 | export const after = () => {
160 | console.log(`
161 | please run following command to start dev server
162 |
163 | - yarn run dev
164 | `);
165 | };
166 |
--------------------------------------------------------------------------------
/scripts/webpack.config.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @since 2016-05-29 10:50
3 | * @author vivaxy
4 | */
5 |
6 | const path = require('path');
7 | const glob = require('glob');
8 | const webpack = require('webpack');
9 | const numeral = require('numeral');
10 | const logUpdate = require('log-update');
11 | const autoprefixer = require('autoprefixer');
12 | const HtmlWebpackPlugin = require('html-webpack-plugin');
13 | const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
14 |
15 | const config = require('./config');
16 |
17 | const DEVELOPMENT_IP = config.DEVELOPMENT_IP;
18 | const DEVELOPMENT_PORT = config.DEVELOPMENT_PORT;
19 | const RELEASE_PATH = config.RELEASE_PATH;
20 | const SOURCE_PATH = 'src';
21 | const DEVELOPMENT = 'development';
22 | const PRODUCTION = 'production';
23 | const NODE_MODULES = 'node_modules';
24 | const ENTRIES_FOLDER = 'entries';
25 | const HTML_FOLDER = 'html';
26 | const COMMON_CHUNK_NAME = 'common';
27 |
28 | const BANNER = '@2016 vivaxy';
29 | const baseDir = path.join(__dirname, '..');
30 |
31 | const NODE_ENV = process.env.NODE_ENV || PRODUCTION;
32 |
33 | const postcssLoader = {
34 | loader: 'postcss-loader',
35 | options: {
36 | plugins: () => {
37 | return [
38 | autoprefixer,
39 | ];
40 | },
41 | },
42 | };
43 |
44 | const eslintRule = {
45 | enforce: 'pre',
46 | test: /\.js$/,
47 | include: [
48 | path.resolve(baseDir, SOURCE_PATH),
49 | ],
50 | loader: 'eslint-loader',
51 | };
52 |
53 | const jsRule = {
54 | test: /\.js$/,
55 | include: [
56 | path.resolve(baseDir, SOURCE_PATH),
57 | ],
58 | use: [
59 | 'babel-loader',
60 | ],
61 | };
62 |
63 | const cssRule = {
64 | test: /\.css$/,
65 | include: [
66 | path.resolve(baseDir, SOURCE_PATH),
67 | ],
68 | use: [
69 | 'style-loader',
70 | 'css-loader',
71 | postcssLoader,
72 | ],
73 | };
74 |
75 | const cssModuleRule = {
76 | test: /\.css$/,
77 | include: [
78 | path.resolve(baseDir, NODE_MODULES),
79 | ],
80 | use: [
81 | 'style-loader',
82 | 'css-loader',
83 | postcssLoader,
84 | ],
85 | };
86 |
87 | const lessRule = {
88 | test: /\.less$/,
89 | include: [
90 | path.resolve(baseDir, SOURCE_PATH),
91 | ],
92 | use: [
93 | 'style-loader',
94 | 'css-loader',
95 | postcssLoader,
96 | 'less-loader',
97 | ],
98 | };
99 |
100 | const lessModuleRule = {
101 | test: /\.less$/,
102 | include: [
103 | path.resolve(baseDir, NODE_MODULES),
104 | ],
105 | use: [
106 | 'style-loader',
107 | 'css-loader',
108 | postcssLoader,
109 | 'less-loader',
110 | ],
111 | };
112 |
113 | const fileRule = {
114 | test: /\.(png|jpg|gif)$/,
115 | use: [
116 | {
117 | loader: 'url-loader',
118 | options: {
119 | limit: 8192,
120 | name: 'images/[name]-[hash].[ext]',
121 | },
122 | },
123 | ],
124 | };
125 |
126 | const createEntryAndPlugins = () => {
127 | const entry = {};
128 | const plugins = [];
129 |
130 | // get entry
131 | const entryFileNameList = glob.sync(`${path.join(SOURCE_PATH, ENTRIES_FOLDER)}/*.js`);
132 | const entryNameList = entryFileNameList.map((entryFileName) => {
133 | return path.basename(entryFileName, '.js');
134 | });
135 |
136 | // get corresponding html template
137 | const htmlFileNameList = glob.sync(`${path.join(SOURCE_PATH, HTML_FOLDER)}/*.html`);
138 | const htmlNameList = htmlFileNameList.map((htmlFileName) => {
139 | return path.basename(htmlFileName, '.html');
140 | });
141 |
142 | // set entry
143 | entryNameList.forEach((entryName) => {
144 | entry[entryName] = [
145 | path.join(baseDir, `./${SOURCE_PATH}/${ENTRIES_FOLDER}/${entryName}.js`),
146 | ];
147 |
148 | let htmlTemplateName = 'index';
149 | if (htmlNameList.indexOf(entryName) !== -1) {
150 | htmlTemplateName = entryName;
151 | }
152 |
153 | plugins.push(new HtmlWebpackPlugin({
154 | template: `${SOURCE_PATH}/${HTML_FOLDER}/${htmlTemplateName}.html`,
155 | filename: `${HTML_FOLDER}/${entryName}.html`,
156 | hash: true,
157 | inject: 'body',
158 | chunks: [
159 | COMMON_CHUNK_NAME,
160 | entryName,
161 | ],
162 | }));
163 | });
164 | return {
165 | entry,
166 | plugins,
167 | };
168 | };
169 |
170 | const addDevToEntry = (entry) => {
171 | const newEntry = {};
172 | Object.keys(entry).forEach((entryName) => {
173 | const ent = entry[entryName];
174 | newEntry[entryName] = [
175 | `webpack-dev-server/client?http://${DEVELOPMENT_IP}:${DEVELOPMENT_PORT}`,
176 | 'webpack/hot/log-apply-result',
177 | // webpackConfig.entry[entryName].unshift('webpack/hot/dev-server');
178 | 'webpack/hot/only-dev-server',
179 | // support react-hot-loader@3, @see https://github.com/gaearon/react-hot-loader/tree/next-docs
180 | 'react-hot-loader/patch',
181 | ...ent,
182 | ];
183 | });
184 | return newEntry;
185 | };
186 |
187 | let { entry, plugins } = createEntryAndPlugins();
188 | let devtool = 'source-map';
189 |
190 | // set config according to environment
191 | switch (NODE_ENV) {
192 | case DEVELOPMENT:
193 | devtool = 'eval';
194 | // support react-hot-loader@3, @see https://github.com/gaearon/react-hot-loader/tree/next-docs
195 | jsRule.use.push('react-hot-loader/webpack');
196 | entry = addDevToEntry(entry);
197 | plugins = [
198 | ...plugins,
199 | new webpack.HotModuleReplacementPlugin(),
200 | new webpack.NoEmitOnErrorsPlugin(),
201 | ];
202 | break;
203 | default:
204 | devtool = 'source-map';
205 | plugins = [
206 | ...plugins,
207 | new webpack.BannerPlugin({
208 | banner: BANNER,
209 | }),
210 | new webpack.optimize.UglifyJsPlugin({
211 | sourceMap: true,
212 | }),
213 | new BundleAnalyzerPlugin({
214 | analyzerMode: 'static',
215 | reportFilename: 'report.html',
216 | openAnalyzer: false,
217 | }),
218 | ];
219 | break;
220 | }
221 |
222 | // default webpack config
223 | module.exports = {
224 | entry,
225 | output: {
226 | path: path.resolve(baseDir, RELEASE_PATH),
227 | filename: 'js/[name].js',
228 | publicPath: '../',
229 | },
230 | module: {
231 | rules: [
232 | eslintRule,
233 | jsRule,
234 | cssRule,
235 | lessRule,
236 | fileRule,
237 | cssModuleRule,
238 | lessModuleRule,
239 | ],
240 | },
241 | plugins: [
242 | ...plugins,
243 | new webpack.EnvironmentPlugin([
244 | 'NODE_ENV',
245 | ]),
246 | new webpack.optimize.CommonsChunkPlugin({
247 | names: [COMMON_CHUNK_NAME],
248 | // pages rests in different folder levels
249 | filename: 'js/[name].js',
250 | minChunks: 2, // Infinity
251 | }),
252 | new webpack.NamedModulesPlugin(),
253 | new webpack.ProgressPlugin((percentage, msg) => {
254 | logUpdate(' progress:', numeral(percentage).format('00.00%'), msg);
255 | }),
256 | ],
257 | devtool,
258 | };
259 |
--------------------------------------------------------------------------------
/src/api/news.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @since 2016-07-31 18:41
3 | * @author vivaxy
4 | */
5 |
6 | import fetch from '../lib/fetch';
7 |
8 | export default async() => {
9 | const result = await fetch({
10 | url: '/newsByPage',
11 | data: Math.random(),
12 | });
13 |
14 | return result.list;
15 | };
16 |
--------------------------------------------------------------------------------
/src/components/DemoButton.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @since 2016-07-31 18:44
3 | * @author vivaxy
4 | */
5 |
6 | import React, { Component } from 'react';
7 | import PropTypes from 'prop-types';
8 | import { Button } from 'react-pianist/Button';
9 |
10 | export default class extends Component {
11 |
12 | static propTypes = {
13 | buttonDisabled: PropTypes.bool.isRequired,
14 | onLoadMore: PropTypes.func.isRequired,
15 | };
16 |
17 | constructor(props) {
18 | super(props);
19 | this.onClick = ::this.onClick;
20 | }
21 |
22 | onClick() {
23 | const {
24 | onLoadMore,
25 | } = this.props;
26 | onLoadMore();
27 | }
28 |
29 | render() {
30 | const {
31 | buttonDisabled,
32 | } = this.props;
33 |
34 | return ;
35 | }
36 |
37 | }
38 |
--------------------------------------------------------------------------------
/src/components/Logo.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @since 2016-08-06 10:09
3 | * @author vivaxy
4 | */
5 |
6 | import React, { Component } from 'react';
7 |
8 | import logoImage from '../images/vivaxy.20150726.jpg';
9 |
10 | const style = {
11 | width: '100px',
12 | height: '100px',
13 | };
14 |
15 | export default class Logo extends Component {
16 | render() {
17 | return (
18 |
19 |

20 |
21 | );
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/components/Toast.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @since 2016-08-06 16:56
3 | * @author vivaxy
4 | */
5 |
6 | import React, { Component } from 'react';
7 | import PropTypes from 'prop-types';
8 | import { AutoHideToast } from 'react-pianist/Toast';
9 |
10 | import * as toastConfig from '../config/toast';
11 |
12 | export default class extends Component {
13 |
14 | static propTypes = {
15 | toastState: PropTypes.shape({
16 | show: PropTypes.bool.isRequired,
17 | message: PropTypes.string.isRequired,
18 | }).isRequired,
19 | hideToastAction: PropTypes.func.isRequired,
20 | };
21 |
22 | constructor(props) {
23 | super(props);
24 | this.onClose = ::this.onClose;
25 | }
26 |
27 | onClose() {
28 | const {
29 | hideToastAction,
30 | } = this.props;
31 | hideToastAction();
32 | }
33 |
34 | render() {
35 | const {
36 | toastState,
37 | } = this.props;
38 |
39 | return (
40 |
45 | {toastState.message}
46 |
47 | );
48 | }
49 |
50 | }
51 |
--------------------------------------------------------------------------------
/src/config/actionTypes.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @since 2017-04-10 10:47:44
3 | * @author vivaxy
4 | */
5 |
6 | export const APPEND_NEWS = 'APPEND_NEWS';
7 |
8 | export const BUTTON_DISABLED = 'BUTTON_DISABLED';
9 | export const BUTTON_DEFAULT = 'BUTTON_DEFAULT';
10 |
11 | export const SHOW_TOAST = 'SHOW_TOAST';
12 | export const HIDE_TOAST = 'HIDE_TOAST';
13 |
--------------------------------------------------------------------------------
/src/config/cookie.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @since 2016-09-01 13:15
3 | * @author vivaxy
4 | */
5 |
6 | export const LOGIN_TOKEN = 'login-token';
7 |
--------------------------------------------------------------------------------
/src/config/entries.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @since 2016-09-01 08:15
3 | * @author vivaxy
4 | */
5 |
6 | export const INDEX = 'index';
7 | export const ABOUT = 'about';
8 |
--------------------------------------------------------------------------------
/src/config/environments.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @since 2016-08-07 12:59
3 | * @author vivaxy
4 | */
5 |
6 | export const DEVELOPMENT = 'development';
7 | export const LOCAL = 'local';
8 | export const BETA = 'beta';
9 | export const PRODUCTION = 'production';
10 |
--------------------------------------------------------------------------------
/src/config/errors.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @since 2016-08-25 11:58
3 | * @author vivaxy
4 | */
5 |
6 | export const FETCH = 'FETCH';
7 | export const SERVER = 'SERVER';
8 | export const TIMEOUT = 'TIMEOUT';
9 |
--------------------------------------------------------------------------------
/src/config/requestMethod.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @since 2016-08-25 13:08
3 | * @author vivaxy
4 | */
5 |
6 | export const GET = 'GET';
7 | export const POST = 'POST';
8 |
--------------------------------------------------------------------------------
/src/config/routes.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @since 2016-10-23 17:02
3 | * @author vivaxy
4 | */
5 |
6 | export const BASE = '/';
7 | export const WILDCARD = '*';
8 | export const DEMO = 'demo';
9 |
--------------------------------------------------------------------------------
/src/config/storage.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @since 2016-09-01 13:15
3 | * @author vivaxy
4 | */
5 |
6 | export const USER_ID = 'user-id';
7 | export const USER_NAME = 'username';
8 | export const USER_AVATAR = 'user-avatar';
9 |
--------------------------------------------------------------------------------
/src/config/toast.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @since 2016-09-01 08:34
3 | * @author vivaxy
4 | */
5 |
6 | export const AUTO_HIDE_DURATION = 2000;
7 |
--------------------------------------------------------------------------------
/src/containers/Demo/PianistDemo.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @since 2016-09-18 11:14
3 | * @author vivaxy
4 | */
5 |
6 | import React, { Component } from 'react';
7 | import PropTypes from 'prop-types';
8 | import { connect } from 'react-redux';
9 |
10 | import { VerticalFlexBox, VerticalFlexItem } from 'react-pianist/VerticalFlex';
11 | import { HorizontalFlexBox, HorizontalFlexItem } from 'react-pianist/HorizontalFlex';
12 | import { BorderBox, BorderLine } from 'react-pianist/BorderBox';
13 | import { Button } from 'react-pianist/Button';
14 |
15 | import { push as routingPushAction } from '../../redux/routing';
16 | import setTitle from '../../lib/setTitle';
17 | import * as i18n from '../../i18n/zh-cn';
18 |
19 | class PianistDemo extends Component {
20 |
21 | static propTypes = {
22 | routingPush: PropTypes.func.isRequired,
23 | params: PropTypes.object.isRequired,
24 | };
25 |
26 | constructor(props) {
27 | super(props);
28 | this.getToIndex = ::this.getToIndex;
29 | this.getNavigationItemStyle = ::this.getNavigationItemStyle;
30 | this.navigate = ::this.navigate;
31 | }
32 |
33 | componentDidMount() {
34 | setTitle(i18n.$SOMEONE_S_HOME('react-pianist'));
35 | }
36 |
37 | getToIndex() {
38 | const {
39 | routingPush,
40 | } = this.props;
41 | routingPush('/');
42 | }
43 |
44 | getNavigationItemStyle = (currentIndex) => {
45 | const {
46 | params: { index },
47 | } = this.props;
48 | return {
49 | textAlign: 'center',
50 | lineHeight: '50px',
51 | color: index === currentIndex ? '#f63' : '#333',
52 | };
53 | };
54 |
55 | navigate(index) {
56 | const {
57 | routingPush,
58 | } = this.props;
59 | return () => {
60 | routingPush(`/demo/${index}`);
61 | };
62 | }
63 |
64 | render() {
65 | return (
66 |
67 |
74 |
75 |
76 |
77 | {Math.random()}
78 |
79 |
80 |
81 |
86 |
87 |
88 |
93 | home
94 |
95 |
100 | contact
101 |
102 |
107 | discovery
108 |
109 |
114 | me
115 |
116 |
117 |
118 |
119 |
120 |
121 | );
122 | }
123 | }
124 |
125 | export default connect(null, {
126 | routingPush: routingPushAction,
127 | })(PianistDemo);
128 |
--------------------------------------------------------------------------------
/src/containers/Index/Home.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @since 2016-10-23 14:48
3 | * @author vivaxy
4 | */
5 |
6 | import React, { Component } from 'react';
7 | import PropTypes from 'prop-types';
8 | import { connect } from 'react-redux';
9 |
10 | import { Button } from 'react-pianist/Button';
11 |
12 | import setTitle from '../../lib/setTitle';
13 |
14 | import Logo from '../../components/Logo';
15 | import DemoButton from '../../components/DemoButton';
16 |
17 | import i18n from '../../i18n/index';
18 | import { getMoreNews } from '../../redux/newsList';
19 | import { push } from '../../redux/routing';
20 | import { showToast } from '../../redux/toast';
21 |
22 | let newsIndex = 0;
23 |
24 | const raisedButtonStyle = {
25 | padding: '0 8px',
26 | };
27 |
28 | class Home extends Component {
29 |
30 | static propTypes = {
31 | routingPush: PropTypes.func.isRequired,
32 | buttonState: PropTypes.bool.isRequired,
33 | newsListState: PropTypes.array.isRequired,
34 | getMoreNewsAction: PropTypes.func.isRequired,
35 | };
36 |
37 | constructor(props) {
38 | super(props);
39 | this.goToDemo = ::this.goToDemo;
40 | }
41 |
42 | componentDidMount() {
43 | const { getMoreNewsAction } = this.props;
44 | setTitle(i18n.$SOMEONE_S_HOME('vivaxy'));
45 | getMoreNewsAction();
46 | }
47 |
48 | goToDemo(index) {
49 | const {
50 | routingPush,
51 | } = this.props;
52 | return () => {
53 | routingPush(`/demo/${index}`);
54 | };
55 | }
56 |
57 | render() {
58 | const {
59 | buttonState,
60 | newsListState,
61 | getMoreNewsAction,
62 | } = this.props;
63 |
64 | return (
65 |
66 |
67 | {newsListState.map((news) => {
68 | return
{news.name}
;
69 | })}
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 | );
81 | }
82 |
83 | }
84 |
85 | export default connect((state) => {
86 | return {
87 | buttonState: state.button,
88 | newsListState: state.newsList,
89 | };
90 | }, {
91 | getMoreNewsAction: getMoreNews,
92 | routingPush: push,
93 | showToastAction: showToast,
94 | })(Home);
95 |
--------------------------------------------------------------------------------
/src/containers/common/Base.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @since 2016-09-01 08:18
3 | * @author vivaxy
4 | */
5 |
6 | import React, { Component, cloneElement } from 'react';
7 | import PropTypes from 'prop-types';
8 | import { connect } from 'react-redux';
9 |
10 | import ReactCSSTransitionGroup from 'react-addons-css-transition-group';
11 |
12 | import Toast from './Toast';
13 |
14 | import './Base.less';
15 |
16 | class Base extends Component {
17 |
18 | static propTypes = {
19 | children: PropTypes.node.isRequired,
20 | location: PropTypes.object.isRequired,
21 | };
22 |
23 | render() {
24 | const {
25 | children,
26 | location,
27 | } = this.props;
28 |
29 | const {
30 | action,
31 | pathname,
32 | } = location;
33 |
34 | const transitionName = `page-transition-${action.toLowerCase()}`;
35 |
36 | return (
37 |
38 |
44 | {cloneElement(children, {
45 | key: pathname,
46 | })}
47 |
48 |
49 |
50 | );
51 | }
52 | }
53 |
54 | export default connect(null, {})(Base);
55 |
--------------------------------------------------------------------------------
/src/containers/common/Base.less:
--------------------------------------------------------------------------------
1 | /**
2 | * @since 2017-01-05 21:15
3 | * @author vivaxy
4 | */
5 |
6 | .pageTransitionStyle {
7 | position: fixed;
8 | top: 0;
9 | left: 0;
10 | width: 100%;
11 | height: 100%;
12 | overflow: auto;
13 | }
14 |
15 | .page-transition-push-enter, .page-transition-pop-leave.page-transition-pop-leave-active {
16 | transform: translate3d(100%, 0, 100px);
17 | transition: all 300ms ease;
18 | z-index: 1;
19 | box-shadow: rgba(0, 0, 0, 0.5) 0 0 0;
20 | .pageTransitionStyle
21 | }
22 |
23 | .page-transition-push-enter.page-transition-push-enter-active, .page-transition-pop-leave {
24 | transform: translate3d(0, 0, 0);
25 | box-shadow: rgba(0, 0, 0, 0.5) 0 0 20px;
26 | .pageTransitionStyle
27 | }
28 |
29 | .page-transition-push-leave, .page-transition-pop-enter.page-transition-pop-enter-active {
30 | transform: translate3d(0, 0, 0);
31 | opacity: 1;
32 | transition: all 300ms ease;
33 | .pageTransitionStyle
34 | }
35 |
36 | .page-transition-push-leave.page-transition-push-leave-active, .page-transition-pop-enter {
37 | transform: translate3d(-50%, 0, -100px);
38 | opacity: 0;
39 | .pageTransitionStyle
40 | }
41 |
42 | .page-wrapper {
43 | background-color: #fff;
44 | }
45 |
--------------------------------------------------------------------------------
/src/containers/common/NoMatch.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @since 2016-09-01 08:18
3 | * @author vivaxy
4 | */
5 |
6 | import React, { Component } from 'react';
7 | import PropTypes from 'prop-types';
8 | import { connect } from 'react-redux';
9 |
10 | class NoMatch extends Component {
11 |
12 | static propTypes = {
13 | params: PropTypes.object.isRequired,
14 | location: PropTypes.object.isRequired,
15 | };
16 |
17 | render() {
18 | const {
19 | params,
20 | location,
21 | } = this.props;
22 |
23 | return (
24 |
25 | {JSON.stringify(params)}
26 | {JSON.stringify(location)}
27 |
28 | );
29 | }
30 |
31 | }
32 |
33 | export default connect(null, {})(NoMatch);
34 |
--------------------------------------------------------------------------------
/src/containers/common/Toast.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @since 2016-11-24 10:26
3 | * @author vivaxy
4 | */
5 |
6 | import { connect } from 'react-redux';
7 |
8 | import Toast from '../../components/Toast';
9 | import { hideToast } from '../../redux/toast';
10 |
11 | const mapStateToProps = (state) => {
12 | return {
13 | toastState: state.toast,
14 | };
15 | };
16 |
17 | export default connect(mapStateToProps, {
18 | hideToastAction: hideToast,
19 | })(Toast);
20 |
--------------------------------------------------------------------------------
/src/entries/about.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @since 2016-09-03 09:16
3 | * @author vivaxy
4 | */
5 |
6 | import 'babel-polyfill';
7 | import page from '../pages/about';
8 |
9 | page();
10 |
11 | /* eslint-disable global-require */
12 | if (module.hot) {
13 | module.hot.accept('../pages/about', () => {
14 | const newPage = require('../pages/about').default;
15 | newPage();
16 | });
17 | }
18 |
--------------------------------------------------------------------------------
/src/entries/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @since 2016-09-03 09:16
3 | * @author vivaxy
4 | */
5 |
6 | import 'babel-polyfill';
7 | import page from '../pages/index';
8 |
9 | page();
10 |
11 | /* eslint-disable global-require */
12 | if (module.hot) {
13 | module.hot.accept('../pages/index', () => {
14 | const newPage = require('../pages/index').default;
15 | newPage();
16 | });
17 | }
18 |
--------------------------------------------------------------------------------
/src/errors/FetchError.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @since 2016-06-05 11:40
3 | * @author vivaxy
4 | */
5 |
6 | import * as constant from '../config/errors';
7 |
8 | export default class FetchError extends Error {
9 | constructor(response) {
10 | super(response.statusText);
11 | this.response = response;
12 | this.name = constant.FETCH;
13 | }
14 | }
15 |
16 | FetchError.create = (response) => {
17 | return new FetchError(response);
18 | };
19 |
--------------------------------------------------------------------------------
/src/errors/ServerError.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @since 2016-09-01 12:57
3 | * @author vivaxy
4 | */
5 |
6 | import * as constant from '../config/errors';
7 |
8 | export default class ServerError extends Error {
9 | constructor(response) {
10 | super(response.message);
11 | this.response = response;
12 | this.name = constant.SERVER;
13 | }
14 | }
15 |
16 | ServerError.create = (response) => {
17 | return new ServerError(response);
18 | };
19 |
--------------------------------------------------------------------------------
/src/errors/TimeoutError.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @since 2016-06-05 11:40
3 | * @author vivaxy
4 | */
5 |
6 | import * as constant from '../config/errors';
7 |
8 | export default class TimeoutError extends Error {
9 | constructor(timeout) {
10 | super('timeout');
11 | this.timeout = timeout;
12 | this.name = constant.TIMEOUT;
13 | }
14 | }
15 |
16 | TimeoutError.create = (timeout) => {
17 | return new TimeoutError(timeout);
18 | };
19 |
--------------------------------------------------------------------------------
/src/errors/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @since 2016-06-05 12:13
3 | * @author vivaxy
4 | */
5 |
6 | import FetchError from './FetchError';
7 | import ServerError from './ServerError';
8 | import TimeoutError from './TimeoutError';
9 |
10 | export {
11 | FetchError,
12 | ServerError,
13 | TimeoutError,
14 | };
15 |
--------------------------------------------------------------------------------
/src/html/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | gt-react-scaffold
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/i18n/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @since 2016-08-17 12:02
3 | * @author vivaxy
4 | */
5 |
6 | import * as zhCN from './zh-cn';
7 |
8 | export default zhCN;
9 |
--------------------------------------------------------------------------------
/src/i18n/zh-cn.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @since 2016-08-17 12:02
3 | * @author vivaxy
4 | */
5 |
6 | /* eslint-disable arrow-body-style */
7 | export const $SOMEONE_S_HOME = (name) => `${name}的个人主页`;
8 | export const NAME = '姓名';
9 |
--------------------------------------------------------------------------------
/src/images/vivaxy.20150726.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vivaxy/gt-react-scaffold/086082ebcb41401639706c23cae929e07ef046ae/src/images/vivaxy.20150726.jpg
--------------------------------------------------------------------------------
/src/lib/checkLogin.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @since 2016-09-01 13:14
3 | * @author vivaxy
4 | */
5 |
6 | import cookie from 'tiny-cookie';
7 |
8 | import * as cookieKey from '../config/cookie';
9 | import * as storageKey from '../config/storage';
10 |
11 | const get = (key) => {
12 | return localStorage.getItem(key);
13 | };
14 |
15 | export default (setLoginInfoAction) => {
16 | let login = false;
17 | if (cookie.get(cookieKey.LOGIN_TOKEN)) {
18 | // get user info from localStorage
19 | setLoginInfoAction({
20 | id: get(storageKey.USER_ID),
21 | name: get(storageKey.USER_NAME),
22 | avatar: get(storageKey.USER_AVATAR),
23 | });
24 | login = true;
25 | }
26 | return login;
27 | };
28 |
--------------------------------------------------------------------------------
/src/lib/environment.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @since 2016-06-11 15:43
3 | * @author vivaxy
4 | */
5 |
6 | import * as environmentConstant from '../config/environments';
7 |
8 | export default process.env.NODE_ENV || environmentConstant.PRODUCTION;
9 |
--------------------------------------------------------------------------------
/src/lib/fetch.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @since 2016-06-05 09:45
3 | * @author vivaxy
4 | */
5 |
6 | import url from 'url';
7 | import fetch from 'isomorphic-fetch';
8 |
9 | import * as requestMethodConstant from '../config/requestMethod';
10 | import getRequestPath from './requestPath';
11 | import { FetchError, ServerError, TimeoutError } from '../errors';
12 | import sleep from './sleep';
13 | import environment from './environment';
14 | import * as environmentType from '../config/environments';
15 |
16 | const MOCK_DELAY = 1000;
17 | const SUCCESS_CODE_LOWER_BOUND = 200;
18 | const SUCCESS_CODE_HIGHER_BOUND = 300;
19 |
20 | const basicFetchOptions = {
21 | credentials: 'include',
22 | };
23 |
24 | const sendRequest = async(config) => {
25 | let response = null;
26 | const {
27 | method = requestMethodConstant.GET,
28 | data,
29 | url: passedURL,
30 | } = config;
31 |
32 | let requestPath = getRequestPath(passedURL);
33 | const requestMethod = method.toUpperCase();
34 |
35 | switch (requestMethod) {
36 | case requestMethodConstant.GET:
37 | requestPath = url.format({
38 | pathname: requestPath,
39 | query: data,
40 | });
41 | response = await fetch(requestPath, {
42 | ...basicFetchOptions,
43 | });
44 | break;
45 | case requestMethodConstant.POST:
46 | response = await fetch(requestPath, {
47 | ...basicFetchOptions,
48 | requestMethod,
49 | body: JSON.stringify(data),
50 | });
51 | break;
52 | default:
53 | break;
54 | }
55 |
56 | if (environment === environmentType.DEVELOPMENT) {
57 | await sleep(MOCK_DELAY);
58 | }
59 |
60 | return response;
61 | };
62 |
63 | const timeoutPromise = async(timeout) => {
64 | await sleep(timeout);
65 | throw new TimeoutError(timeout);
66 | };
67 |
68 | export default async(config) => {
69 | const {
70 | timeout = 60000,
71 | ...fetchConfig
72 | } = config;
73 |
74 | const response = await Promise.race([timeoutPromise(timeout), sendRequest(fetchConfig)]);
75 |
76 | if (response.status < SUCCESS_CODE_LOWER_BOUND || response.status >= SUCCESS_CODE_HIGHER_BOUND) {
77 | throw new FetchError(response);
78 | } else {
79 | const resp = await response.json();
80 | if (resp.code !== 0) {
81 | throw new ServerError(resp);
82 | } else {
83 | return resp.data;
84 | }
85 | }
86 | };
87 |
--------------------------------------------------------------------------------
/src/lib/getURLParameter.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @since 2016-08-17 14:30
3 | * @author vivaxy
4 | */
5 |
6 | import url from 'url';
7 |
8 | export default (key, href = location.href) => {
9 | const o = url.parse(href, true);
10 | return o.query[key];
11 | };
12 |
--------------------------------------------------------------------------------
/src/lib/logout.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @since 2016-08-27 10:27
3 | * @author vivaxy
4 | */
5 |
6 | import cookie from 'tiny-cookie';
7 |
8 | import * as cookieKey from '../config/cookie';
9 |
10 | export default () => {
11 | cookie.remove(cookieKey.LOGIN_TOKEN);
12 | };
13 |
--------------------------------------------------------------------------------
/src/lib/processErrors.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @since 2017-04-10 10:42:13
3 | * @author vivaxy
4 | */
5 |
6 | import * as errorType from '../config/errors';
7 | import { showToast } from '../redux/toast';
8 |
9 | export default (ex) => {
10 | switch (ex.name) {
11 | case errorType.FETCH:
12 | showToast(ex.message);
13 | break;
14 | case errorType.SERVER:
15 | showToast(ex.message);
16 | break;
17 | default:
18 | throw ex;
19 | }
20 | };
21 |
--------------------------------------------------------------------------------
/src/lib/render.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @since 2016-07-31 19:09
3 | * @author vivaxy
4 | */
5 |
6 | import { render } from 'react-dom';
7 | import { Provider } from 'react-redux';
8 | import { AppContainer } from 'react-hot-loader';
9 | import { Router, hashHistory } from 'react-router';
10 | import { syncHistoryWithStore } from 'react-router-redux';
11 | import React, { Children, createElement } from 'react';
12 |
13 | import setStyle from './style';
14 | import store from './store';
15 |
16 | const ID_SELECTOR = 'gt-react-scaffold';
17 | const history = syncHistoryWithStore(hashHistory, store);
18 |
19 | setStyle();
20 |
21 | // hack around https://github.com/gaearon/react-hot-boilerplate/pull/61#issuecomment-211504531
22 | Router.prototype.componentWillReceiveProps = (nextProps) => {
23 | const components = [];
24 |
25 | function grabComponents(element) {
26 | // This only works for JSX routes, adjust accordingly for plain JS config
27 | if (element.props && element.props.component) {
28 | components.push(element.props.component);
29 | }
30 | if (element.props && element.props.children) {
31 | Children.forEach(element.props.children, grabComponents);
32 | }
33 | }
34 |
35 | grabComponents(nextProps.routes || nextProps.children);
36 | components.forEach(createElement); // force patching
37 | };
38 |
39 | export default (routes) => {
40 | return render(
41 |
42 |
43 |
44 |
45 | ,
46 | document.getElementById(ID_SELECTOR),
47 | );
48 | };
49 |
--------------------------------------------------------------------------------
/src/lib/requestPath.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @since 2016-08-14 13:48
3 | * @author vivaxy
4 | */
5 |
6 | import * as environmentConstant from '../config/environments';
7 |
8 | import environment from './environment';
9 |
10 | const MOCK_API_ROOT = '/api';
11 |
12 | export default (url) => {
13 | let requestPath = '';
14 | switch (environment) {
15 | case environmentConstant.DEVELOPMENT:
16 | requestPath = `${MOCK_API_ROOT}${url}.json`;
17 | break;
18 | case environmentConstant.PRODUCTION:
19 | requestPath = `${MOCK_API_ROOT}${url}.json`;
20 | break;
21 | default:
22 | break;
23 | }
24 | return requestPath;
25 | };
26 |
--------------------------------------------------------------------------------
/src/lib/setTitle.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @since 2016-08-13 14:38
3 | * @author vivaxy
4 | */
5 |
6 | export default (title) => {
7 | document.title = title;
8 | };
9 |
--------------------------------------------------------------------------------
/src/lib/sleep.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @since 2016-08-06 15:57
3 | * @author vivaxy
4 | */
5 |
6 | export default (timeout) => {
7 | return new Promise((resolve) => {
8 | setTimeout(resolve, timeout);
9 | });
10 | };
11 |
--------------------------------------------------------------------------------
/src/lib/store.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @since 2016-09-16 18:11
3 | * @author vivaxy
4 | */
5 |
6 | import { createStore, combineReducers, compose, applyMiddleware } from 'redux';
7 | import { routerMiddleware } from 'react-router-redux';
8 | import { hashHistory } from 'react-router';
9 | import thunk from 'redux-thunk';
10 |
11 | import reducers from '../redux/reducers';
12 |
13 | const routing = routerMiddleware(hashHistory);
14 |
15 | const store = createStore(combineReducers(reducers), compose(
16 | applyMiddleware(thunk, routing),
17 | window.devToolsExtension ? window.devToolsExtension() : (f) => f, // eslint-disable-line arrow-body-style
18 | ));
19 |
20 | if (module.hot) {
21 | // Enable Webpack hot module replacement for reducers
22 | module.hot.accept('../redux/reducers', () => {
23 | const nextRootReducer = require('../redux/reducers').default; // eslint-disable-line global-require
24 | store.replaceReducer(combineReducers(nextRootReducer));
25 | });
26 | }
27 |
28 | export default store;
29 |
--------------------------------------------------------------------------------
/src/lib/style.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @since 2016-08-06 12:03
3 | * @author vivaxy
4 | */
5 |
6 | import iconImage from '../images/vivaxy.20150726.jpg';
7 | import './style.less';
8 |
9 | export default () => {
10 | const style = {
11 | margin: '0',
12 | };
13 |
14 | const body = document.body;
15 |
16 | Object.keys(style).forEach((key) => {
17 | body.style[key] = style[key];
18 | });
19 |
20 | const head = document.head;
21 | const icon = document.createElement('link');
22 | icon.type = 'image/png';
23 | icon.rel = 'shortcut icon';
24 | icon.href = iconImage;
25 | head.appendChild(icon);
26 | };
27 |
--------------------------------------------------------------------------------
/src/lib/style.less:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vivaxy/gt-react-scaffold/086082ebcb41401639706c23cae929e07ef046ae/src/lib/style.less
--------------------------------------------------------------------------------
/src/pages/about.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @since 2016-10-24 09:01
3 | * @author vivaxy
4 | */
5 |
6 | import render from '../lib/render';
7 | import routes from '../routes/about';
8 |
9 | export default () => {
10 | return render(routes);
11 | };
12 |
--------------------------------------------------------------------------------
/src/pages/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @since 2016-10-24 09:01
3 | * @author vivaxy
4 | */
5 |
6 | import render from '../lib/render';
7 | import routes from '../routes/index';
8 |
9 | export default () => {
10 | return render(routes);
11 | };
12 |
--------------------------------------------------------------------------------
/src/redux/button.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @since 2016-07-31 18:40
3 | * @author vivaxy
4 | */
5 |
6 | import { createReducer } from 'redux-create-reducer';
7 |
8 | import { BUTTON_DEFAULT, BUTTON_DISABLED, APPEND_NEWS } from '../config/actionTypes';
9 |
10 | const defaultState = true;
11 |
12 | export default createReducer(defaultState, {
13 | [BUTTON_DISABLED]: () => {
14 | return false;
15 | },
16 | [BUTTON_DEFAULT]: () => {
17 | return true;
18 | },
19 | [APPEND_NEWS]: () => {
20 | return true;
21 | },
22 | });
23 |
24 | export const setButtonDisabled = () => {
25 | return {
26 | type: BUTTON_DISABLED,
27 | };
28 | };
29 |
30 | export const setButtonDefault = () => {
31 | return {
32 | type: BUTTON_DEFAULT,
33 | };
34 | };
35 |
--------------------------------------------------------------------------------
/src/redux/newsList.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @since 2016-08-06 16:07
3 | * @author vivaxy
4 | */
5 |
6 | import { createReducer } from 'redux-create-reducer';
7 |
8 | import processErrors from '../lib/processErrors';
9 |
10 | import { APPEND_NEWS } from '../config/actionTypes';
11 | import { setButtonDisabled, setButtonDefault } from './button';
12 | import getNewsAPI from '../api/news';
13 |
14 | const defaultState = [];
15 |
16 | export default createReducer(defaultState, {
17 | [APPEND_NEWS]: (state, action) => {
18 | return [...state, ...action.list];
19 | },
20 | });
21 |
22 | export const getMoreNews = () => {
23 | return async(dispatch) => {
24 | try {
25 | dispatch(setButtonDisabled());
26 | const list = await getNewsAPI();
27 | dispatch({
28 | list,
29 | type: APPEND_NEWS,
30 | });
31 | } catch (ex) {
32 | dispatch(setButtonDefault());
33 | processErrors(ex);
34 | }
35 | };
36 | };
37 |
--------------------------------------------------------------------------------
/src/redux/pagination.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @since 2016-08-31 17:11
3 | * @author vivaxy
4 | */
5 |
6 | import PropTypes from 'prop-types';
7 | import { createReducer } from 'redux-create-reducer';
8 |
9 | const SETUP_PAGINATION = 'SETUP_PAGINATION';
10 |
11 | const defaultState = {
12 | pageIndex: 0,
13 | pageSize: 0,
14 | pageCount: 0,
15 | rowCount: 0,
16 | needMore: true,
17 | };
18 |
19 | export default createReducer(defaultState, {
20 | [SETUP_PAGINATION]: (state, action) => {
21 | return {
22 | ...state,
23 | pageSize: action.pageSize,
24 | pageIndex: action.pageIndex,
25 | pageCount: action.pageCount,
26 | rowCount: action.rowCount,
27 | needMore: action.pageIndex !== action.pageCount,
28 | };
29 | },
30 | });
31 |
32 | export const propTypes = PropTypes.shape({
33 | pageIndex: PropTypes.number.isRequired,
34 | pageSize: PropTypes.number.isRequired,
35 | pageCount: PropTypes.number.isRequired,
36 | rowCount: PropTypes.number.isRequired,
37 | needMore: PropTypes.bool.isRequired,
38 | });
39 |
--------------------------------------------------------------------------------
/src/redux/reducers.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @since 2016-08-01 21:50
3 | * @author vivaxy
4 | */
5 |
6 | import { routerReducer } from 'react-router-redux';
7 |
8 | import button from './button';
9 | import newsList from './newsList';
10 | import toast from './toast';
11 | import pagination from './pagination';
12 |
13 | export default {
14 | button,
15 | newsList,
16 | toast,
17 | pagination,
18 | routing: routerReducer,
19 | };
20 |
--------------------------------------------------------------------------------
/src/redux/routing.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @since 2017-04-10 09:55:20
3 | * @author vivaxy
4 | */
5 |
6 | import {
7 | push as routerPush,
8 | replace as routerReplace,
9 | go as routerGo,
10 | goBack as routerGoBack,
11 | goForward as routerGoFroward,
12 | } from 'react-router-redux';
13 |
14 | export const push = (location) => {
15 | return routerPush(location);
16 | };
17 | export const replace = (location) => {
18 | return routerReplace(location);
19 | };
20 | export const go = (number) => {
21 | return routerGo(number);
22 | };
23 | export const goBack = () => {
24 | return routerGoBack();
25 | };
26 | export const goForward = () => {
27 | return routerGoFroward();
28 | };
29 |
--------------------------------------------------------------------------------
/src/redux/toast.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @since 2016-08-25 09:31
3 | * @author vivaxy
4 | */
5 |
6 | import { createReducer } from 'redux-create-reducer';
7 |
8 | import { HIDE_TOAST, SHOW_TOAST } from '../config/actionTypes';
9 |
10 | const defaultState = {
11 | show: false,
12 | message: '',
13 | };
14 |
15 | export default createReducer(defaultState, {
16 | [SHOW_TOAST]: (state, action) => {
17 | return {
18 | ...state,
19 | show: true,
20 | message: action.message,
21 | };
22 | },
23 | [HIDE_TOAST]: (state) => {
24 | return {
25 | ...state,
26 | show: false,
27 | message: '',
28 | };
29 | },
30 | });
31 |
32 | export const showToast = (message) => {
33 | return {
34 | message,
35 | type: SHOW_TOAST,
36 | };
37 | };
38 |
39 | export const hideToast = () => {
40 | return {
41 | type: HIDE_TOAST,
42 | };
43 | };
44 |
--------------------------------------------------------------------------------
/src/routes/about.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @since 2016-10-24 09:02
3 | * @author vivaxy
4 | */
5 |
6 | import Base from '../containers/common/Base';
7 | import NoMatch from '../containers/common/NoMatch';
8 | import Index from '../containers/Index/Home';
9 | import Demo from '../containers/Demo/PianistDemo';
10 |
11 | const demoRoute = {
12 | path: 'demo/:index',
13 | component: Demo,
14 | };
15 |
16 | const noMatchRoute = {
17 | path: '*',
18 | component: NoMatch,
19 | };
20 |
21 | const route = {
22 | path: '/',
23 | component: Base,
24 | indexRoute: {
25 | component: Index,
26 | },
27 | childRoutes: [
28 | demoRoute,
29 | noMatchRoute,
30 | ],
31 | };
32 |
33 | export default route;
34 |
--------------------------------------------------------------------------------
/src/routes/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @since 2016-10-24 09:02
3 | * @author vivaxy
4 | */
5 |
6 | import Base from '../containers/common/Base';
7 | import NoMatch from '../containers/common/NoMatch';
8 | import Index from '../containers/Index/Home';
9 | import Demo from '../containers/Demo/PianistDemo';
10 |
11 | const demoRoute = {
12 | path: 'demo/:index',
13 | component: Demo,
14 | };
15 |
16 | const noMatchRoute = {
17 | path: '*',
18 | component: NoMatch,
19 | };
20 |
21 | const route = {
22 | path: '/',
23 | component: Base,
24 | indexRoute: {
25 | component: Index,
26 | },
27 | childRoutes: [
28 | demoRoute,
29 | noMatchRoute,
30 | ],
31 | };
32 |
33 | export default route;
34 |
--------------------------------------------------------------------------------
/test/get-base-dirname.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @since 2016-10-24 08:47
3 | * @author vivaxy
4 | */
5 |
6 | import test from 'ava';
7 | import path from 'path';
8 |
9 | test('project base directory name', (t) => {
10 | const paths = path.resolve(__dirname, '..');
11 | t.true(paths === process.cwd());
12 | });
13 |
--------------------------------------------------------------------------------