Compare commits
310 Commits
before-ref
...
multy-serv
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
64a1cb27c2 | ||
|
|
c03248de48 | ||
|
|
17cb1075cf | ||
|
|
787560916b | ||
|
|
06828c2d8b | ||
|
|
baf6fbbb2b | ||
|
|
5b26f6b203 | ||
|
|
423eb1ed7b | ||
|
|
0142f55f09 | ||
|
|
8fd467b1d0 | ||
|
|
198632f94f | ||
|
|
7c885ae2e6 | ||
|
|
36284e507d | ||
|
|
e6e4969695 | ||
|
|
370a8622c3 | ||
|
|
93e0346538 | ||
|
|
d657a08d94 | ||
|
|
9eab89a72b | ||
|
|
32267b766a | ||
|
|
b80ca1d852 | ||
|
|
5cb179b055 | ||
|
|
70ebc7b2ce | ||
|
|
25eb5c845d | ||
|
|
d3d4e95a19 | ||
|
|
357cf192ac | ||
|
|
40aacdd809 | ||
|
|
251a894262 | ||
|
|
bec6434f73 | ||
|
|
b294c09e1a | ||
|
|
62ec4aae1f | ||
|
|
8f20d6e43c | ||
|
|
4cc8cc225c | ||
|
|
508a60ab2a | ||
|
|
92f1389680 | ||
|
|
5368193ad0 | ||
|
|
fd7fff3fb6 | ||
|
|
b0056b66b8 | ||
|
|
a2b9b065d0 | ||
|
|
fb414b02d8 | ||
|
|
946459a104 | ||
|
|
415bd3b091 | ||
|
|
05a3f37a6e | ||
|
|
ef6fa75486 | ||
|
|
d755218f5f | ||
|
|
f61fbcc44f | ||
|
|
ded7785170 | ||
|
|
eb084c8a8c | ||
|
|
66bcef28c6 | ||
|
|
bfa0c4d9eb | ||
|
|
2ed1c99435 | ||
|
|
3aa171aef8 | ||
|
|
1312ff142c | ||
|
|
4d26b88153 | ||
|
|
4bbc210ef1 | ||
|
|
6b8e921cc4 | ||
|
|
09c663b11c | ||
|
|
8036ffc595 | ||
|
|
446e2e5970 | ||
|
|
d69598bd56 | ||
|
|
af18b825fe | ||
|
|
f5ccd6e4b7 | ||
|
|
fd1561de07 | ||
|
|
1e28ed8f25 | ||
|
|
f90dae3f96 | ||
|
|
08098584b3 | ||
|
|
efef5f932f | ||
|
|
fe5fa2eecd | ||
|
|
c03a5cc434 | ||
|
|
49ee30bcf1 | ||
|
|
1b86bd7912 | ||
|
|
cd89395cf1 | ||
|
|
b129dc5cc6 | ||
|
|
58b81e6cf4 | ||
|
|
38ec23f4fa | ||
|
|
b767fb4aca | ||
|
|
9c60f4d228 | ||
|
|
2e9d82bc31 | ||
|
|
413dc91a48 | ||
|
|
184578fcc4 | ||
|
|
405ac1c0bd | ||
|
|
acd8ddb7bb | ||
|
|
e8b8a195bc | ||
|
|
d384ef2d59 | ||
|
|
f7a38f55b9 | ||
|
|
afae0ea8fb | ||
|
|
dd727b3fac | ||
|
|
2a2067cd0f | ||
|
|
f533eb0b05 | ||
|
|
36a628e5de | ||
|
|
096bf67079 | ||
|
|
9254a6d1da | ||
|
|
dfddb2b3af | ||
|
|
fcd385b2b0 | ||
|
|
5b2e59ed55 | ||
|
|
ee3e5ac150 | ||
|
|
e74c33a88b | ||
|
|
2f28609cd5 | ||
|
|
055d97075d | ||
|
|
ab9cc5aab3 | ||
|
|
0e83ea4dd1 | ||
|
|
e8e5b06a91 | ||
|
|
9fca36b281 | ||
|
|
0e735102ff | ||
|
|
44fe38a5d9 | ||
|
|
80d24021ef | ||
|
|
ce2cf6d7a8 | ||
|
|
f30aeb1d20 | ||
|
|
de8720ee60 | ||
|
|
4c1b0b3e1f | ||
|
|
545626a94e | ||
|
|
b54c72c074 | ||
|
|
d0a66c605e | ||
|
|
44833471da | ||
|
|
8fc0b33189 | ||
|
|
35fad3ef5c | ||
|
|
fb26280a78 | ||
|
|
e115ebc208 | ||
|
|
5ad9c7e4fc | ||
|
|
ae21729bd7 | ||
|
|
6e750ff63b | ||
|
|
26f45e7602 | ||
|
|
4f87a5c2cd | ||
|
|
311edd0dce | ||
|
|
7c18fa9002 | ||
|
|
86ee569794 | ||
|
|
2e71f596c1 | ||
|
|
5b367b61c9 | ||
|
|
aebd647d35 | ||
|
|
cd5c5af94f | ||
|
|
b897b513b8 | ||
|
|
2cb1888f8d | ||
|
|
fa4e08c8e2 | ||
|
|
84bc94538f | ||
|
|
faac196af7 | ||
|
|
3be0520b60 | ||
|
|
8945082220 | ||
|
|
ada458bac6 | ||
|
|
e479093746 | ||
|
|
92adf9c8d5 | ||
|
|
641577665f | ||
|
|
e5de0b5f45 | ||
|
|
43b29fbecc | ||
|
|
c50eee237c | ||
|
|
2fa2ed62ba | ||
|
|
205f1bef51 | ||
|
|
7af202a198 | ||
|
|
03b4318c7a | ||
|
|
b2a3fe4ce8 | ||
|
|
648a6062a9 | ||
|
|
5047a0ddbf | ||
|
|
60269f2ada | ||
|
|
3d0daa6d60 | ||
|
|
063b147448 | ||
|
|
6094a6c2fd | ||
|
|
976a1260b2 | ||
|
|
09b685ca86 | ||
|
|
99a1812b35 | ||
|
|
73861d6a18 | ||
|
|
b7e775f854 | ||
|
|
9468a589fc | ||
|
|
af028923e5 | ||
|
|
9d46b7c195 | ||
|
|
b42975ac30 | ||
|
|
91ff195ab8 | ||
|
|
855835af53 | ||
|
|
0c272532b3 | ||
|
|
74c823edeb | ||
|
|
8e624fdb49 | ||
|
|
cb66cdff20 | ||
|
|
abcc81649c | ||
|
|
737ade5c46 | ||
|
|
0be99d845d | ||
|
|
4298b90a6f | ||
|
|
23162bb324 | ||
|
|
24d7f83394 | ||
|
|
c722103b39 | ||
|
|
ea00b664a3 | ||
|
|
12facb0ecd | ||
|
|
6afc119adf | ||
|
|
88aac37021 | ||
|
|
b9319e801b | ||
|
|
25aa59c067 | ||
|
|
6d943da96e | ||
|
|
174525550c | ||
|
|
e37d73c7ac | ||
|
|
5168520efb | ||
|
|
5caf77d0c3 | ||
|
|
15d181a415 | ||
|
|
81aca317fc | ||
|
|
0b9ac1206d | ||
|
|
c95361d0f0 | ||
|
|
d2a439c04e | ||
|
|
b50417f1a0 | ||
|
|
ae1f934b39 | ||
|
|
df20030b9f | ||
|
|
091bffcb52 | ||
|
|
11d81a212e | ||
|
|
5b5239d18d | ||
|
|
f48f59d1a7 | ||
|
|
8f6c48ad0a | ||
|
|
3921ee099c | ||
|
|
8d6ad16498 | ||
|
|
0b397ac36a | ||
|
|
d472987c2c | ||
|
|
45b597d9ff | ||
|
|
59cdb70f9e | ||
|
|
09f17d60f8 | ||
|
|
5b45f88281 | ||
|
|
8802b797d1 | ||
|
|
b5814cdb1f | ||
|
|
f08e1bce8e | ||
|
|
224f2bfd51 | ||
|
|
18b9b8d0e2 | ||
|
|
f045304019 | ||
|
|
30cc932ef9 | ||
|
|
a49eab3955 | ||
|
|
5326bab255 | ||
|
|
684f9c791c | ||
|
|
683c53838b | ||
|
|
e136eb090c | ||
|
|
133963766a | ||
|
|
77082119cc | ||
|
|
31f4a4a37e | ||
|
|
357a957afd | ||
|
|
fdc2c8a554 | ||
|
|
f5c87c1aeb | ||
|
|
317cf4cb11 | ||
|
|
0f3e71300f | ||
|
|
5db767254c | ||
|
|
e5503fcff6 | ||
|
|
0b0e420a5a | ||
|
|
70e24b3d0e | ||
|
|
3b48625597 | ||
|
|
f499c818da | ||
|
|
4610f14394 | ||
|
|
313526207f | ||
|
|
03d1fca234 | ||
|
|
3c4acae3bd | ||
|
|
10c38506fa | ||
|
|
ba1cab4fcb | ||
|
|
0a5e13dc04 | ||
|
|
d40df8622c | ||
|
|
9672e6b7f0 | ||
|
|
578e33eb4b | ||
|
|
a036070a33 | ||
|
|
95e0cf5918 | ||
|
|
02a7dfdde7 | ||
|
|
c8a1f9c3b8 | ||
|
|
739756e553 | ||
|
|
23eed87017 | ||
|
|
d633557d30 | ||
|
|
dce00ac8b2 | ||
|
|
b017014c60 | ||
|
|
9352424aa7 | ||
|
|
15a86e4830 | ||
|
|
86b3cec1e7 | ||
|
|
e38c6b4916 | ||
|
|
3acdfa09ae | ||
|
|
1f5324a975 | ||
|
|
10d1ea5983 | ||
|
|
a7605b589c | ||
|
|
e5945e48dd | ||
|
|
313c3b577b | ||
|
|
d2e1866582 | ||
|
|
277bcd2759 | ||
|
|
3223dcad66 | ||
|
|
cb93fded6c | ||
|
|
2ef0ad78e1 | ||
|
|
a88446478e | ||
|
|
fdcf280630 | ||
|
|
617d809569 | ||
|
|
fa317e6518 | ||
|
|
97cf0b346e | ||
|
|
beb34935a2 | ||
|
|
268687f6c3 | ||
|
|
12f5742c59 | ||
|
|
dac52c2b8b | ||
|
|
7c742dc880 | ||
|
|
d66136e7bf | ||
|
|
69fe1fd412 | ||
|
|
a943e4f946 | ||
|
|
5924cad2db | ||
|
|
e12e658b56 | ||
|
|
b8f03fd2ac | ||
|
|
9be8790052 | ||
|
|
fb8efe91aa | ||
|
|
4437d60cf8 | ||
|
|
697282f6ef | ||
|
|
5b0c720c4f | ||
|
|
a97a132320 | ||
|
|
2be239d1fe | ||
|
|
96ea6c0a92 | ||
|
|
417b291f1f | ||
|
|
f66af6c706 | ||
|
|
8b83889659 | ||
|
|
aba076cf36 | ||
|
|
1838d3c26f | ||
|
|
37a7901f5d | ||
|
|
2fef514437 | ||
|
|
437a32cb2b | ||
|
|
d66b71d0a1 | ||
|
|
63516582cc | ||
|
|
cc30bd0a08 | ||
|
|
50af09ba70 | ||
|
|
11bcc10137 | ||
|
|
dfc54b7b00 | ||
|
|
58bfbf0703 | ||
|
|
c22fe03bf3 | ||
|
|
caae675135 | ||
|
|
bc0eff5c55 |
9
.gitignore
vendored
@@ -1,6 +1,15 @@
|
|||||||
# Dependency directory
|
# Dependency directory
|
||||||
node_modules
|
node_modules
|
||||||
|
|
||||||
|
# Build directories
|
||||||
|
src/public/dist/local/bundle/
|
||||||
|
src/public/dist/standalone/bundle/
|
||||||
|
|
||||||
|
# example for dev
|
||||||
|
# example-project
|
||||||
|
|
||||||
# Remove some common IDE working directories
|
# Remove some common IDE working directories
|
||||||
.idea
|
.idea
|
||||||
.DS_STORE
|
.DS_STORE
|
||||||
|
|
||||||
|
yarn.lock
|
||||||
3
.npmignore
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
.idea
|
||||||
|
example-project
|
||||||
|
docs
|
||||||
42
LICENSE
@@ -1,21 +1,29 @@
|
|||||||
MIT License
|
BSD 3-Clause License
|
||||||
|
|
||||||
Copyright (c) 2018 Bohdan Liashenko
|
Copyright (c) 2019, Bohdan Liashenko
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Redistribution and use in source and binary forms, with or without
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
modification, are permitted provided that the following conditions are met:
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
* Redistributions of source code must retain the above copyright notice, this
|
||||||
copies or substantial portions of the Software.
|
list of conditions and the following disclaimer.
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
* Redistributions in binary form must reproduce the above copyright notice,
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
this list of conditions and the following disclaimer in the documentation
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
and/or other materials provided with the distribution.
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
* Neither the name of the copyright holder nor the names of its
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
contributors may be used to endorse or promote products derived from
|
||||||
SOFTWARE.
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||||
|
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
|
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|||||||
50
README.md
@@ -1,2 +1,48 @@
|
|||||||
# codecrumbs
|
# codecrumbs [active development phase, stay tuned!] [](https://twitter.com/intent/tweet?text=Leave%20breadcrumbs%20in%20source%20code%20with%20codecrumbs%20tool%20&url=https://github.com/Bogdan-Lyashenko/codecrumbs&via=bliashenko&hashtags=javascript,code)
|
||||||
Leave "breadcrumbs" in source code via comments to find your way out from code maze
|
|
||||||
|
[](https://badge.fury.io/js/codecrumbs)
|
||||||
|
|
||||||
|
Leave "breadcrumbs" in source code via comments to find your way out from code maze.
|
||||||
|
> Still much work to do, but the basic features are already implemented and are ready to use. Give it a try while I am finishing a few more big features. Ideas and improvements are welcome. Thanks.
|
||||||
|
|
||||||
|
## [Demo](https://codecrumbs.io/)
|
||||||
|
Check out [**standalone version here**](https://codecrumbs.io/) with defined trail of codecrumbs.
|
||||||
|
|
||||||
|
[<img src="/docs/codecrumbs-ui.png" width="800">](https://codecrumbs.io/)
|
||||||
|
|
||||||
|
## Get started
|
||||||
|
Install ```codecrumbs``` globally or in ```devDependencies```:
|
||||||
|
```yarn add codecrumbs -D```
|
||||||
|
|
||||||
|
Add command with **entry file** and **source directory** to ```scripts``` section in your ```package.json```. Change ```-e``` (entry point file), ```-d``` (directory) params to match paths inside your project.
|
||||||
|
```javascript
|
||||||
|
// package.json
|
||||||
|
...
|
||||||
|
"scripts": {
|
||||||
|
"start:cc": "codecrumbs -e src/index.js -d src"
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
Run ```yarn start:cc``` from the terminal. Go to [http://localhost:2018/#](http://localhost:2018/#) in the browser to check it out.
|
||||||
|
|
||||||
|
## Breadcrumbs
|
||||||
|
> Leave breadcrumbs by simply putting a comment in code, diagram wil be updated on the fly!
|
||||||
|
|
||||||
|
Write ```//cc:here is breadcrumb``` to put a simple breadcrumb in the code. ```cc``` (stands for "codecrumb") is the prefix which used by the parser, and ```here is breadcrumb``` is a title of our first breadcrumb.
|
||||||
|
|
||||||
|
Also, you can create “trail of breadcrumbs” — basically, a sequence of codecrumbs which follow some data flow (e.g. user login, or form submit, etc.).
|
||||||
|
To create a codecrumb as part of a trail you write: ```//cc:signin#3;enable route``` where ```signin``` is the **trail ID**, ```#3``` is order **number of step**, ```enable route``` is a title describing the step.
|
||||||
|
|
||||||
|
<img src="/docs/live-changes.gif" width="800">
|
||||||
|
|
||||||
|
Check out [the introduction article here](https://itnext.io/how-to-navigate-the-maze-of-javascript-code-541250447cbb) for more details.
|
||||||
|
|
||||||
|
|
||||||
|
## Learn and share your knowledge
|
||||||
|
So let’s say you put together some trail of codecrumbs describing some important flow inside the project. How you can share it with others? Simply download the json file of codecrumbs store, send it to the friend, he/she uploads it to the codecrumbs and can see same you just saw!
|
||||||
|
<img src="/docs/share-knowledge.gif" width="800">
|
||||||
|
|
||||||
|
## Support
|
||||||
|
|
||||||
|
If you like this project and believe it makes sense, please, put a :star: or tweet about it - it will show your support and motivate me :punch:. Thanks!
|
||||||
|
|||||||
1
_config.yml
Normal file
@@ -0,0 +1 @@
|
|||||||
|
theme: jekyll-theme-cayman
|
||||||
36
cli/index.cli.js
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
const program = require('commander');
|
||||||
|
const colors = require('colors');
|
||||||
|
|
||||||
|
const server = require('../src/server');
|
||||||
|
|
||||||
|
program
|
||||||
|
.option('-e, --entry [entryFile]', 'Specify path to entry point file. E.g. `./src/app.js`')
|
||||||
|
.option('-d, --dir [projectDir]', 'Specify path to project source code directory. E.g. `./src`')
|
||||||
|
.option(
|
||||||
|
'-w, --webpack [webpackConfigFile]',
|
||||||
|
'Specify path to webpack config file. E.g. ./webpack.config.js'
|
||||||
|
)
|
||||||
|
.option('-p, --port [defaultPort]', 'Specify port for Codecrumbs client. E.g. 3333', 2018)
|
||||||
|
.parse(process.argv);
|
||||||
|
|
||||||
|
if (!program.entry && !program.dir) {
|
||||||
|
console.log(
|
||||||
|
colors.magenta(
|
||||||
|
'Please specify `entry` and `dir` params. E.g. `codecrumbs -e src/app.js -d src`'
|
||||||
|
)
|
||||||
|
);
|
||||||
|
process.exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
server.setup(
|
||||||
|
{
|
||||||
|
projectNameAlias: undefined, // TODO: add param for this
|
||||||
|
entryPoint: program.entry,
|
||||||
|
projectDir: program.dir,
|
||||||
|
webpackConfigPath: program.webpack,
|
||||||
|
clientPort: program.port
|
||||||
|
},
|
||||||
|
false
|
||||||
|
);
|
||||||
3
commitlint.config.js
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
module.exports = {
|
||||||
|
extends: ['@commitlint/config-conventional']
|
||||||
|
};
|
||||||
BIN
docs/codecrumbs-ui.png
Normal file
|
After Width: | Height: | Size: 198 KiB |
BIN
docs/live-changes.gif
Normal file
|
After Width: | Height: | Size: 7.0 MiB |
BIN
docs/share-knowledge.gif
Normal file
|
After Width: | Height: | Size: 3.3 MiB |
@@ -1,3 +0,0 @@
|
|||||||
import move from '../zoom/move';
|
|
||||||
|
|
||||||
export default () => Promise.resolve({ data: [] });
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
import tabs from './views/tabs';
|
|
||||||
import dataModel from './dataModel/model';
|
|
||||||
|
|
||||||
const App = {
|
|
||||||
init() {
|
|
||||||
tabs.render(); //cc:render;CallLong line check out tabs.js for more details
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
App.init();
|
|
||||||
6
example-project/src/auth/action-types.js
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
export const INIT_AUTH = 'INIT_AUTH';
|
||||||
|
|
||||||
|
export const SIGN_IN_ERROR = 'SIGN_IN_ERROR';
|
||||||
|
export const SIGN_IN_SUCCESS = 'SIGN_IN_SUCCESS';
|
||||||
|
|
||||||
|
export const SIGN_OUT_SUCCESS = 'SIGN_OUT_SUCCESS';
|
||||||
72
example-project/src/auth/actions.js
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
import firebase from 'firebase';
|
||||||
|
import { firebaseAuth } from '../firebase';
|
||||||
|
import {
|
||||||
|
INIT_AUTH,
|
||||||
|
SIGN_IN_ERROR,
|
||||||
|
SIGN_IN_SUCCESS,
|
||||||
|
SIGN_OUT_SUCCESS
|
||||||
|
} from './action-types';
|
||||||
|
|
||||||
|
|
||||||
|
function authenticate(provider) {
|
||||||
|
return dispatch => {
|
||||||
|
//cc:signin#1;firebase sign in;+2
|
||||||
|
firebaseAuth.signInWithPopup(provider)
|
||||||
|
.then(result => dispatch(signInSuccess(result)))
|
||||||
|
.catch(error => dispatch(signInError(error)));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function initAuth(user) {
|
||||||
|
return {
|
||||||
|
type: INIT_AUTH,
|
||||||
|
payload: user
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function signInError(error) {
|
||||||
|
return {
|
||||||
|
type: SIGN_IN_ERROR,
|
||||||
|
payload: error
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function signInSuccess(result) {
|
||||||
|
return {
|
||||||
|
type: SIGN_IN_SUCCESS,
|
||||||
|
payload: result.user
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function signInWithGithub() {
|
||||||
|
return authenticate(new firebase.auth.GithubAuthProvider());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function signInWithGoogle() {
|
||||||
|
return authenticate(new firebase.auth.GoogleAuthProvider());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function signInWithTwitter() {
|
||||||
|
return authenticate(new firebase.auth.TwitterAuthProvider());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function signOut() {
|
||||||
|
return dispatch => {
|
||||||
|
firebaseAuth.signOut()
|
||||||
|
.then(() => dispatch(signOutSuccess()));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function signOutSuccess() {
|
||||||
|
return {
|
||||||
|
type: SIGN_OUT_SUCCESS
|
||||||
|
};
|
||||||
|
}
|
||||||
16
example-project/src/auth/auth.js
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { firebaseAuth } from '../firebase';
|
||||||
|
import * as authActions from './actions';
|
||||||
|
|
||||||
|
|
||||||
|
export function initAuth(dispatch) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const unsubscribe = firebaseAuth.onAuthStateChanged(
|
||||||
|
authUser => {
|
||||||
|
dispatch(authActions.initAuth(authUser));
|
||||||
|
unsubscribe();
|
||||||
|
resolve();
|
||||||
|
},
|
||||||
|
error => reject(error)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
8
example-project/src/auth/index.js
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import * as authActions from './actions';
|
||||||
|
|
||||||
|
|
||||||
|
export { authActions };
|
||||||
|
export * from './action-types';
|
||||||
|
export { initAuth } from './auth';
|
||||||
|
export { authReducer } from './reducer';
|
||||||
|
export { getAuth, isAuthenticated } from './selectors';
|
||||||
26
example-project/src/auth/reducer.js
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import { Record } from 'immutable';
|
||||||
|
import { INIT_AUTH, SIGN_IN_SUCCESS, SIGN_OUT_SUCCESS } from './action-types';
|
||||||
|
|
||||||
|
|
||||||
|
export const AuthState = new Record({
|
||||||
|
authenticated: false,
|
||||||
|
id: null
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
export function authReducer(state = new AuthState(), {payload, type}) {
|
||||||
|
switch (type) {
|
||||||
|
case INIT_AUTH:
|
||||||
|
case SIGN_IN_SUCCESS:
|
||||||
|
return state.merge({
|
||||||
|
authenticated: !!payload, // cc:signin#2;toggle 'authenticated' flag
|
||||||
|
id: payload ? payload.uid : null
|
||||||
|
});
|
||||||
|
|
||||||
|
case SIGN_OUT_SUCCESS:
|
||||||
|
return new AuthState();
|
||||||
|
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
}
|
||||||
16
example-project/src/auth/selectors.js
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { createSelector } from 'reselect';
|
||||||
|
|
||||||
|
|
||||||
|
export function isAuthenticated(state) {
|
||||||
|
return getAuth(state).authenticated;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//=====================================
|
||||||
|
// MEMOIZED SELECTORS
|
||||||
|
//-------------------------------------
|
||||||
|
|
||||||
|
export const getAuth = createSelector(
|
||||||
|
state => state.auth,
|
||||||
|
auth => auth.toJS()
|
||||||
|
);
|
||||||
6
example-project/src/firebase/config.js
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
export const firebaseConfig = {
|
||||||
|
apiKey: 'AIzaSyBsVVpEDrlNPEmshLcmOuE0FxhjPn0AqMg',
|
||||||
|
authDomain: 'todo-react-redux.firebaseapp.com',
|
||||||
|
databaseURL: 'https://todo-react-redux.firebaseio.com',
|
||||||
|
storageBucket: 'firebase-todo-react-redux.appspot.com'
|
||||||
|
};
|
||||||
86
example-project/src/firebase/firebase-list.js
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
import { firebaseDb } from './firebase';
|
||||||
|
|
||||||
|
|
||||||
|
export class FirebaseList {
|
||||||
|
constructor(actions, modelClass, path = null) {
|
||||||
|
this._actions = actions;
|
||||||
|
this._modelClass = modelClass;
|
||||||
|
this._path = path;
|
||||||
|
}
|
||||||
|
|
||||||
|
get path() {
|
||||||
|
return this._path;
|
||||||
|
}
|
||||||
|
|
||||||
|
set path(value) {
|
||||||
|
this._path = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
push(value) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
firebaseDb.ref(this._path)
|
||||||
|
.push(value, error => error ? reject(error) : resolve());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
remove(key) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
firebaseDb.ref(`${this._path}/${key}`)
|
||||||
|
.remove(error => error ? reject(error) : resolve());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
set(key, value) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
firebaseDb.ref(`${this._path}/${key}`)
|
||||||
|
.set(value, error => error ? reject(error) : resolve());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
update(key, value) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
firebaseDb.ref(`${this._path}/${key}`)
|
||||||
|
.update(value, error => error ? reject(error) : resolve());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
subscribe(emit) {
|
||||||
|
let ref = firebaseDb.ref(this._path);
|
||||||
|
let initialized = false;
|
||||||
|
let list = [];
|
||||||
|
|
||||||
|
ref.once('value', () => {
|
||||||
|
initialized = true;
|
||||||
|
emit(this._actions.onLoad(list));
|
||||||
|
});
|
||||||
|
|
||||||
|
ref.on('child_added', snapshot => {
|
||||||
|
if (initialized) {
|
||||||
|
emit(this._actions.onAdd(this.unwrapSnapshot(snapshot)));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
list.push(this.unwrapSnapshot(snapshot));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ref.on('child_changed', snapshot => {
|
||||||
|
emit(this._actions.onChange(this.unwrapSnapshot(snapshot)));
|
||||||
|
});
|
||||||
|
|
||||||
|
ref.on('child_removed', snapshot => {
|
||||||
|
emit(this._actions.onRemove(this.unwrapSnapshot(snapshot)));
|
||||||
|
});
|
||||||
|
|
||||||
|
this._unsubscribe = () => ref.off();
|
||||||
|
}
|
||||||
|
|
||||||
|
unsubscribe() {
|
||||||
|
this._unsubscribe();
|
||||||
|
}
|
||||||
|
|
||||||
|
unwrapSnapshot(snapshot) {
|
||||||
|
let attrs = snapshot.val();
|
||||||
|
attrs.key = snapshot.key;
|
||||||
|
return new this._modelClass(attrs);
|
||||||
|
}
|
||||||
|
}
|
||||||
10
example-project/src/firebase/firebase.js
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import firebase from 'firebase/app';
|
||||||
|
|
||||||
|
import 'firebase/auth';
|
||||||
|
import 'firebase/database';
|
||||||
|
|
||||||
|
import { firebaseConfig } from './config';
|
||||||
|
|
||||||
|
export const firebaseApp = firebase.initializeApp(firebaseConfig);
|
||||||
|
export const firebaseAuth = firebase.auth();
|
||||||
|
export const firebaseDb = firebase.database();
|
||||||
2
example-project/src/firebase/index.js
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
export { firebaseApp, firebaseAuth, firebaseDb } from './firebase';
|
||||||
|
export { FirebaseList } from './firebase-list';
|
||||||
4
example-project/src/history.js
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
import createHistory from 'history/createBrowserHistory';
|
||||||
|
|
||||||
|
|
||||||
|
export default createHistory();
|
||||||
44
example-project/src/index.js
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
import './views/styles/styles.css';
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import ReactDOM from 'react-dom';
|
||||||
|
import { Provider } from 'react-redux';
|
||||||
|
import { ConnectedRouter } from 'react-router-redux';
|
||||||
|
|
||||||
|
import { initAuth } from './auth';
|
||||||
|
import history from './history';
|
||||||
|
import configureStore from './store';
|
||||||
|
import registerServiceWorker from './utils/register-service-worker';
|
||||||
|
import App from './views/app';
|
||||||
|
|
||||||
|
|
||||||
|
const store = configureStore();
|
||||||
|
const rootElement = document.getElementById('root');
|
||||||
|
|
||||||
|
//cc:layout#0;start;provider
|
||||||
|
function render(Component) {
|
||||||
|
ReactDOM.render(
|
||||||
|
<Provider store={store}>
|
||||||
|
<ConnectedRouter history={history}>
|
||||||
|
<div>
|
||||||
|
<Component/>
|
||||||
|
</div>
|
||||||
|
</ConnectedRouter>
|
||||||
|
</Provider>,
|
||||||
|
rootElement
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (module.hot) {
|
||||||
|
module.hot.accept('./views/app', () => {
|
||||||
|
render(require('./views/app').default);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
registerServiceWorker();
|
||||||
|
|
||||||
|
initAuth(store.dispatch)
|
||||||
|
.then(() => render(App))
|
||||||
|
.catch(error => console.error(error));
|
||||||
1
example-project/src/notification/action-types.js
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export const DISMISS_NOTIFICATION = 'DISMISS_NOTIFICATION';
|
||||||
8
example-project/src/notification/actions.js
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import { DISMISS_NOTIFICATION } from './action-types';
|
||||||
|
|
||||||
|
|
||||||
|
export function dismissNotification() {
|
||||||
|
return {
|
||||||
|
type: DISMISS_NOTIFICATION
|
||||||
|
};
|
||||||
|
}
|
||||||
7
example-project/src/notification/index.js
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import * as notificationActions from './actions';
|
||||||
|
|
||||||
|
|
||||||
|
export { notificationActions };
|
||||||
|
export * from './action-types';
|
||||||
|
export { notificationReducer } from './reducer';
|
||||||
|
export { getNotification } from './selectors';
|
||||||
28
example-project/src/notification/reducer.js
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import { Record } from 'immutable';
|
||||||
|
import { REMOVE_TASK_SUCCESS } from '../tasks';
|
||||||
|
import { DISMISS_NOTIFICATION } from './action-types';
|
||||||
|
|
||||||
|
|
||||||
|
export const NotificationState = new Record({
|
||||||
|
actionLabel: '',
|
||||||
|
display: false,
|
||||||
|
message: ''
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
export function notificationReducer(state = new NotificationState(), action) {
|
||||||
|
switch (action.type) {
|
||||||
|
case REMOVE_TASK_SUCCESS:
|
||||||
|
return state.merge({
|
||||||
|
actionLabel: 'Undo',
|
||||||
|
display: true,
|
||||||
|
message: 'Task deleted'
|
||||||
|
});
|
||||||
|
|
||||||
|
case DISMISS_NOTIFICATION:
|
||||||
|
return new NotificationState();
|
||||||
|
|
||||||
|
default:
|
||||||
|
return new NotificationState();
|
||||||
|
}
|
||||||
|
}
|
||||||
3
example-project/src/notification/selectors.js
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export function getNotification(state) {
|
||||||
|
return state.notification;
|
||||||
|
}
|
||||||
13
example-project/src/reducers.js
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import { routerReducer } from 'react-router-redux';
|
||||||
|
import { combineReducers } from 'redux';
|
||||||
|
import { authReducer } from './auth';
|
||||||
|
import { notificationReducer } from './notification';
|
||||||
|
import { tasksReducer } from './tasks';
|
||||||
|
|
||||||
|
|
||||||
|
export default combineReducers({
|
||||||
|
auth: authReducer,
|
||||||
|
notification: notificationReducer,
|
||||||
|
routing: routerReducer,
|
||||||
|
tasks: tasksReducer
|
||||||
|
});
|
||||||
27
example-project/src/store.js
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import { routerMiddleware } from 'react-router-redux';
|
||||||
|
import { applyMiddleware, compose, createStore } from 'redux';
|
||||||
|
import thunk from 'redux-thunk';
|
||||||
|
import history from './history';
|
||||||
|
import reducers from './reducers';
|
||||||
|
|
||||||
|
|
||||||
|
export default (initialState = {}) => {
|
||||||
|
let middleware = applyMiddleware(thunk, routerMiddleware(history));
|
||||||
|
|
||||||
|
if (process.env.NODE_ENV !== 'production') {
|
||||||
|
const devToolsExtension = window.devToolsExtension;
|
||||||
|
if (typeof devToolsExtension === 'function') {
|
||||||
|
middleware = compose(middleware, devToolsExtension());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const store = createStore(reducers, initialState, middleware);
|
||||||
|
|
||||||
|
if (module.hot) {
|
||||||
|
module.hot.accept('./reducers', () => {
|
||||||
|
store.replaceReducer(require('./reducers').default);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return store;
|
||||||
|
};
|
||||||
14
example-project/src/tasks/action-types.js
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
export const CREATE_TASK_ERROR = 'CREATE_TASK_ERROR';
|
||||||
|
export const CREATE_TASK_SUCCESS = 'CREATE_TASK_SUCCESS';
|
||||||
|
|
||||||
|
export const REMOVE_TASK_ERROR = 'REMOVE_TASK_ERROR';
|
||||||
|
export const REMOVE_TASK_SUCCESS = 'REMOVE_TASK_SUCCESS';
|
||||||
|
|
||||||
|
export const UNDELETE_TASK_ERROR = 'UNDELETE_TASK_ERROR';
|
||||||
|
|
||||||
|
export const UPDATE_TASK_ERROR = 'UPDATE_TASK_ERROR';
|
||||||
|
export const UPDATE_TASK_SUCCESS = 'UPDATE_TASK_SUCCESS';
|
||||||
|
|
||||||
|
export const FILTER_TASKS = 'FILTER_TASKS';
|
||||||
|
export const LOAD_TASKS_SUCCESS = 'LOAD_TASKS_SUCCESS';
|
||||||
|
export const UNLOAD_TASKS_SUCCESS = 'UNLOAD_TASKS_SUCCESS';
|
||||||
124
example-project/src/tasks/actions.js
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
import { getDeletedTask } from './selectors';
|
||||||
|
import { taskList } from './task-list';
|
||||||
|
import {
|
||||||
|
CREATE_TASK_ERROR,
|
||||||
|
CREATE_TASK_SUCCESS,
|
||||||
|
REMOVE_TASK_ERROR,
|
||||||
|
REMOVE_TASK_SUCCESS,
|
||||||
|
FILTER_TASKS,
|
||||||
|
LOAD_TASKS_SUCCESS,
|
||||||
|
UNDELETE_TASK_ERROR,
|
||||||
|
UNLOAD_TASKS_SUCCESS,
|
||||||
|
UPDATE_TASK_ERROR,
|
||||||
|
UPDATE_TASK_SUCCESS
|
||||||
|
} from './action-types';
|
||||||
|
|
||||||
|
|
||||||
|
export function createTask(title) {
|
||||||
|
return dispatch => {
|
||||||
|
taskList.push({completed: false, title})
|
||||||
|
.catch(error => dispatch(createTaskError(error)));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createTaskError(error) {
|
||||||
|
return {
|
||||||
|
type: CREATE_TASK_ERROR,
|
||||||
|
payload: error
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createTaskSuccess(task) {
|
||||||
|
return {
|
||||||
|
type: CREATE_TASK_SUCCESS,
|
||||||
|
payload: task
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function removeTask(task) {
|
||||||
|
return dispatch => {
|
||||||
|
taskList.remove(task.key)
|
||||||
|
.catch(error => dispatch(removeTaskError(error)));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function removeTaskError(error) {
|
||||||
|
return {
|
||||||
|
type: REMOVE_TASK_ERROR,
|
||||||
|
payload: error
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function removeTaskSuccess(task) {
|
||||||
|
return {
|
||||||
|
type: REMOVE_TASK_SUCCESS,
|
||||||
|
payload: task
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function undeleteTask() {
|
||||||
|
return (dispatch, getState) => {
|
||||||
|
const task = getDeletedTask(getState());
|
||||||
|
if (task) {
|
||||||
|
taskList.set(task.key, {completed: task.completed, title: task.title})
|
||||||
|
.catch(error => dispatch(undeleteTaskError(error)));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function undeleteTaskError(error) {
|
||||||
|
return {
|
||||||
|
type: UNDELETE_TASK_ERROR,
|
||||||
|
payload: error
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateTaskError(error) {
|
||||||
|
return {
|
||||||
|
type: UPDATE_TASK_ERROR,
|
||||||
|
payload: error
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateTask(task, changes) {
|
||||||
|
return dispatch => {
|
||||||
|
taskList.update(task.key, changes)
|
||||||
|
.catch(error => dispatch(updateTaskError(error)));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateTaskSuccess(task) {
|
||||||
|
return {
|
||||||
|
type: UPDATE_TASK_SUCCESS,
|
||||||
|
payload: task
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function loadTasksSuccess(tasks) {
|
||||||
|
return {
|
||||||
|
type: LOAD_TASKS_SUCCESS,
|
||||||
|
payload: tasks
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function filterTasks(filterType) {
|
||||||
|
return {
|
||||||
|
type: FILTER_TASKS,
|
||||||
|
payload: {filterType}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function loadTasks() {
|
||||||
|
return (dispatch, getState) => {
|
||||||
|
const { auth } = getState();
|
||||||
|
taskList.path = `tasks/${auth.id}`;
|
||||||
|
taskList.subscribe(dispatch);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function unloadTasks() {
|
||||||
|
taskList.unsubscribe();
|
||||||
|
return {
|
||||||
|
type: UNLOAD_TASKS_SUCCESS
|
||||||
|
};
|
||||||
|
}
|
||||||
8
example-project/src/tasks/index.js
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import * as tasksActions from './actions';
|
||||||
|
|
||||||
|
|
||||||
|
export { tasksActions };
|
||||||
|
export * from './action-types';
|
||||||
|
export { tasksReducer } from './reducer';
|
||||||
|
export { getTaskFilter, getVisibleTasks } from './selectors';
|
||||||
|
export { Task } from './task';
|
||||||
59
example-project/src/tasks/reducer.js
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
import { List, Record } from 'immutable';
|
||||||
|
import { SIGN_OUT_SUCCESS } from '../auth/action-types';
|
||||||
|
import {
|
||||||
|
CREATE_TASK_SUCCESS,
|
||||||
|
REMOVE_TASK_SUCCESS,
|
||||||
|
FILTER_TASKS,
|
||||||
|
LOAD_TASKS_SUCCESS,
|
||||||
|
UPDATE_TASK_SUCCESS
|
||||||
|
} from './action-types';
|
||||||
|
|
||||||
|
|
||||||
|
export const TasksState = new Record({
|
||||||
|
deleted: null,
|
||||||
|
filter: '',
|
||||||
|
list: new List(),
|
||||||
|
previous: null
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
export function tasksReducer(state = new TasksState(), {payload, type}) {
|
||||||
|
switch (type) {
|
||||||
|
case CREATE_TASK_SUCCESS:
|
||||||
|
return state.merge({
|
||||||
|
deleted: null,
|
||||||
|
previous: null,
|
||||||
|
list: state.deleted && state.deleted.key === payload.key ?
|
||||||
|
state.previous :
|
||||||
|
state.list.unshift(payload)
|
||||||
|
});
|
||||||
|
|
||||||
|
case REMOVE_TASK_SUCCESS:
|
||||||
|
return state.merge({
|
||||||
|
deleted: payload,
|
||||||
|
previous: state.list,
|
||||||
|
list: state.list.filter(task => task.key !== payload.key)
|
||||||
|
});
|
||||||
|
|
||||||
|
case FILTER_TASKS:
|
||||||
|
return state.set('filter', payload.filterType || '');
|
||||||
|
|
||||||
|
case LOAD_TASKS_SUCCESS:
|
||||||
|
return state.set('list', new List(payload.reverse()));
|
||||||
|
|
||||||
|
case UPDATE_TASK_SUCCESS:
|
||||||
|
return state.merge({
|
||||||
|
deleted: null,
|
||||||
|
previous: null,
|
||||||
|
list: state.list.map(task => {
|
||||||
|
return task.key === payload.key ? payload : task;
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
case SIGN_OUT_SUCCESS:
|
||||||
|
return new TasksState();
|
||||||
|
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
}
|
||||||
40
example-project/src/tasks/selectors.js
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
import { createSelector } from 'reselect';
|
||||||
|
|
||||||
|
|
||||||
|
export function getTasks(state) {
|
||||||
|
return state.tasks;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getTaskList(state) {
|
||||||
|
return getTasks(state).list;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getTaskFilter(state) {
|
||||||
|
return getTasks(state).filter;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getDeletedTask(state) {
|
||||||
|
return getTasks(state).deleted;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//=====================================
|
||||||
|
// MEMOIZED SELECTORS
|
||||||
|
//-------------------------------------
|
||||||
|
|
||||||
|
export const getVisibleTasks = createSelector(
|
||||||
|
getTaskList,
|
||||||
|
getTaskFilter,
|
||||||
|
(tasks, filter) => {
|
||||||
|
switch (filter) {
|
||||||
|
case 'active':
|
||||||
|
return tasks.filter(task => !task.completed);
|
||||||
|
|
||||||
|
case 'completed':
|
||||||
|
return tasks.filter(task => task.completed);
|
||||||
|
|
||||||
|
default:
|
||||||
|
return tasks;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
11
example-project/src/tasks/task-list.js
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import { FirebaseList } from '../firebase';
|
||||||
|
import * as taskActions from './actions';
|
||||||
|
import { Task } from './task';
|
||||||
|
|
||||||
|
|
||||||
|
export const taskList = new FirebaseList({
|
||||||
|
onAdd: taskActions.createTaskSuccess,
|
||||||
|
onChange: taskActions.updateTaskSuccess,
|
||||||
|
onLoad: taskActions.loadTasksSuccess,
|
||||||
|
onRemove: taskActions.removeTaskSuccess
|
||||||
|
}, Task);
|
||||||
8
example-project/src/tasks/task.js
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import { Record } from 'immutable';
|
||||||
|
|
||||||
|
|
||||||
|
export const Task = new Record({
|
||||||
|
completed: false,
|
||||||
|
key: null,
|
||||||
|
title: null
|
||||||
|
});
|
||||||
10
example-project/src/utils/create-test-component.js
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { findRenderedComponentWithType, renderIntoDocument } from 'react-dom/test-utils';
|
||||||
|
|
||||||
|
|
||||||
|
export function createTestComponent(TestComponent, props) {
|
||||||
|
return findRenderedComponentWithType(
|
||||||
|
renderIntoDocument(<TestComponent {...props}/>),
|
||||||
|
TestComponent
|
||||||
|
);
|
||||||
|
}
|
||||||
97
example-project/src/utils/register-service-worker.js
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
// In production, we register a service worker to serve assets from local cache.
|
||||||
|
|
||||||
|
// This lets the app load faster on subsequent visits in production, and gives
|
||||||
|
// it offline capabilities. However, it also means that developers (and users)
|
||||||
|
// will only see deployed updates on the "N+1" visit to a page, since previously
|
||||||
|
// cached resources are updated in the background.
|
||||||
|
|
||||||
|
// To learn more about the benefits of this model, read https://goo.gl/KwvDNy.
|
||||||
|
// This link also includes instructions on opting out of this behavior.
|
||||||
|
|
||||||
|
const isLocalhost = Boolean(
|
||||||
|
window.location.hostname === 'localhost' ||
|
||||||
|
// [::1] is the IPv6 localhost address.
|
||||||
|
window.location.hostname === '[::1]' ||
|
||||||
|
// 127.0.0.1/8 is considered localhost for IPv4.
|
||||||
|
window.location.hostname.match(
|
||||||
|
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
export default function register() {
|
||||||
|
const publicUrl = new URL(process.env.PUBLIC_URL, window.location);
|
||||||
|
if (publicUrl.origin !== window.location.origin) return;
|
||||||
|
|
||||||
|
window.addEventListener('load', () => {
|
||||||
|
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
|
||||||
|
if (!isLocalhost) {
|
||||||
|
registerValidSW(swUrl);
|
||||||
|
} else {
|
||||||
|
checkValidServiceWorker(swUrl);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function registerValidSW(swUrl) {
|
||||||
|
navigator.serviceWorker
|
||||||
|
.register(swUrl)
|
||||||
|
.then(registration => {
|
||||||
|
registration.onupdatefound = () => {
|
||||||
|
const installingWorker = registration.installing;
|
||||||
|
installingWorker.onstatechange = () => {
|
||||||
|
if (installingWorker.state === 'installed') {
|
||||||
|
if (navigator.serviceWorker.controller) {
|
||||||
|
// At this point, the old content will have been purged and
|
||||||
|
// the fresh content will have been added to the cache.
|
||||||
|
// It's the perfect time to display a "New content is
|
||||||
|
// available; please refresh." message in your web app.
|
||||||
|
console.log('New content is available; please refresh.');
|
||||||
|
} else {
|
||||||
|
// At this point, everything has been precached.
|
||||||
|
// It's the perfect time to display a
|
||||||
|
// "Content is cached for offline use." message.
|
||||||
|
console.log('Content is cached for offline use.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error during service worker registration:', error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkValidServiceWorker(swUrl) {
|
||||||
|
// Check if the service worker can be found. If it can't reload the page.
|
||||||
|
fetch(swUrl)
|
||||||
|
.then(response => {
|
||||||
|
// Ensure service worker exists, and that we really are getting a JS file.
|
||||||
|
if (
|
||||||
|
response.status === 404 ||
|
||||||
|
response.headers.get('content-type').indexOf('javascript') === -1
|
||||||
|
) {
|
||||||
|
// No service worker found. Probably a different app. Reload the page.
|
||||||
|
navigator.serviceWorker.ready.then(registration => {
|
||||||
|
registration.unregister().then(() => {
|
||||||
|
window.location.reload();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Service worker found. Proceed as normal.
|
||||||
|
registerValidSW(swUrl);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
console.log(
|
||||||
|
'No internet connection found. App is running in offline mode.'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function unregister() {
|
||||||
|
if ('serviceWorker' in navigator) {
|
||||||
|
navigator.serviceWorker.ready.then(registration => {
|
||||||
|
registration.unregister();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
50
example-project/src/views/app/app.js
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { withRouter } from 'react-router-dom';
|
||||||
|
|
||||||
|
import { authActions, getAuth } from '../../auth';
|
||||||
|
import Header from '../components/header';
|
||||||
|
import RequireAuthRoute from '../components/require-auth-route';
|
||||||
|
import RequireUnauthRoute from '../components/require-unauth-route';
|
||||||
|
import SignInPage from '../pages/sign-in';
|
||||||
|
import TasksPage from '../pages/tasks';
|
||||||
|
|
||||||
|
//cc:layout#1;describe pages
|
||||||
|
const App = ({authenticated, signOut}) => (
|
||||||
|
<div>
|
||||||
|
<Header
|
||||||
|
authenticated={authenticated}
|
||||||
|
signOut={signOut}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
<RequireAuthRoute authenticated={authenticated} exact path="/" component={TasksPage}/>
|
||||||
|
<RequireUnauthRoute authenticated={authenticated} path="/sign-in" component={SignInPage}/>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
App.propTypes = {
|
||||||
|
authenticated: PropTypes.bool.isRequired,
|
||||||
|
signOut: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
//=====================================
|
||||||
|
// CONNECT
|
||||||
|
//-------------------------------------
|
||||||
|
|
||||||
|
const mapStateToProps = getAuth;
|
||||||
|
|
||||||
|
const mapDispatchToProps = {
|
||||||
|
signOut: authActions.signOut
|
||||||
|
};
|
||||||
|
|
||||||
|
//cc:here
|
||||||
|
export default withRouter(
|
||||||
|
connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(App)
|
||||||
|
);
|
||||||
1
example-project/src/views/app/index.js
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export { default } from './app';
|
||||||
25
example-project/src/views/components/button/button.js
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
import './button.css';
|
||||||
|
|
||||||
|
|
||||||
|
const Button = ({children, className, onClick, type = 'button'}) => {
|
||||||
|
const cssClasses = classNames('btn', className);
|
||||||
|
return (
|
||||||
|
<button className={cssClasses} onClick={onClick} type={type}>
|
||||||
|
{children}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
Button.propTypes = {
|
||||||
|
children: PropTypes.node,
|
||||||
|
className: PropTypes.string,
|
||||||
|
onClick: PropTypes.func,
|
||||||
|
type: PropTypes.oneOf(['button', 'reset', 'submit'])
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export default Button;
|
||||||
19
example-project/src/views/components/button/button.scss
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
@import 'views/styles/shared';
|
||||||
|
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
@include button-base;
|
||||||
|
outline: none;
|
||||||
|
border: 0;
|
||||||
|
padding: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
transform: translate(0, 0);
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn--icon {
|
||||||
|
border-radius: 40px;
|
||||||
|
padding: 8px;
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
}
|
||||||
1
example-project/src/views/components/button/index.js
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export { default } from './button';
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
|
||||||
|
export default function GitHubLogo() {
|
||||||
|
return (
|
||||||
|
<svg viewBox="0 0 20 20">
|
||||||
|
<path d="M10 0C4.5 0 0 4.5 0 10c0 4.4 2.9 8.2 6.8 9.5.5.1.7-.2.7-.5v-1.9c-2.5.5-3.2-.6-3.4-1.1-.1-.3-.6-1.2-1-1.4-.4-.2-.9-.6 0-.7.8 0 1.3.7 1.5 1 .9 1.5 2.4 1.1 3 .9.1-.6.4-1.1.6-1.3-2.2-.3-4.6-1.2-4.6-5 0-1.1.4-2 1-2.7 0-.3-.4-1.3.2-2.7 0 0 .8-.3 2.8 1 .7-.2 1.6-.3 2.4-.3s1.7.1 2.5.3c1.9-1.3 2.8-1 2.8-1 .5 1.4.2 2.4.1 2.7.6.7 1 1.6 1 2.7 0 3.8-2.3 4.7-4.6 4.9.4.3.7.9.7 1.9v2.8c0 .3.2.6.7.5 4-1.3 6.8-5.1 6.8-9.5C20 4.5 15.5 0 10 0z" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { default } from './github-logo';
|
||||||
33
example-project/src/views/components/header/header.js
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import Button from '../button';
|
||||||
|
import GitHubLogo from '../github-logo';
|
||||||
|
|
||||||
|
import './header.css';
|
||||||
|
|
||||||
|
const Header = ({authenticated, signOut}) => (
|
||||||
|
<header className="header">
|
||||||
|
<div className="g-row">
|
||||||
|
<div className="g-col">
|
||||||
|
<h1 className="header__title">Todo React Redux</h1>
|
||||||
|
|
||||||
|
<ul className="header__actions">
|
||||||
|
{authenticated ? <li><Button onClick={signOut}>Sign out</Button></li> : null}
|
||||||
|
<li>
|
||||||
|
<a className="link link--github" href="https://github.com/r-park/todo-react-redux">
|
||||||
|
<GitHubLogo />
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
);
|
||||||
|
|
||||||
|
Header.propTypes = {
|
||||||
|
authenticated: PropTypes.bool.isRequired,
|
||||||
|
signOut: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export default Header;
|
||||||
73
example-project/src/views/components/header/header.scss
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
@import 'views/styles/shared';
|
||||||
|
|
||||||
|
|
||||||
|
.header {
|
||||||
|
padding: 10px 0;
|
||||||
|
height: 60px;
|
||||||
|
overflow: hidden;
|
||||||
|
line-height: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header__title {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
float: left;
|
||||||
|
font-size: rem(14px);
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 40px;
|
||||||
|
text-rendering: auto;
|
||||||
|
transform: translate(0,0);
|
||||||
|
|
||||||
|
&:before {
|
||||||
|
display: inline-block;
|
||||||
|
border: 2px solid #eee;
|
||||||
|
margin-right: 8px;
|
||||||
|
border-radius: 100%;
|
||||||
|
height: 16px;
|
||||||
|
width: 16px;
|
||||||
|
content: ' ';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.header__actions {
|
||||||
|
@include clearfix;
|
||||||
|
float: right;
|
||||||
|
padding: 8px 0;
|
||||||
|
line-height: 24px;
|
||||||
|
|
||||||
|
li {
|
||||||
|
float: left;
|
||||||
|
list-style: none;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-left: 12px;
|
||||||
|
padding-left: 12px;
|
||||||
|
border-left: 1px solid #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
display: block;
|
||||||
|
margin: 0;
|
||||||
|
color: #999;
|
||||||
|
font-size: rem(14px);
|
||||||
|
line-height: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.link {
|
||||||
|
display: block;
|
||||||
|
fill: #98999a;
|
||||||
|
transform: translate(0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.link--github {
|
||||||
|
padding-top: 1px;
|
||||||
|
width: 22px;
|
||||||
|
height: 24px;
|
||||||
|
}
|
||||||
|
}
|
||||||
1
example-project/src/views/components/header/index.js
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export { default } from './header';
|
||||||
17
example-project/src/views/components/icon/icon.js
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
|
||||||
|
const Icon = ({className, name}) => {
|
||||||
|
const cssClasses = classNames('material-icons', className);
|
||||||
|
return <span className={cssClasses}>{name}</span>;
|
||||||
|
};
|
||||||
|
|
||||||
|
Icon.propTypes = {
|
||||||
|
className: PropTypes.string,
|
||||||
|
name: PropTypes.string.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export default Icon;
|
||||||
1
example-project/src/views/components/icon/index.js
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export { default } from './icon';
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { default } from './notification';
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
import React, { Component } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
import './notification.css';
|
||||||
|
|
||||||
|
|
||||||
|
class Notification extends Component {
|
||||||
|
static propTypes = {
|
||||||
|
action: PropTypes.func.isRequired,
|
||||||
|
actionLabel: PropTypes.string.isRequired,
|
||||||
|
dismiss: PropTypes.func.isRequired,
|
||||||
|
display: PropTypes.bool.isRequired,
|
||||||
|
duration: PropTypes.number,
|
||||||
|
message: PropTypes.string.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.startTimer();
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillReceiveProps(nextProps) {
|
||||||
|
if (nextProps.display) {
|
||||||
|
this.startTimer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
this.clearTimer();
|
||||||
|
}
|
||||||
|
|
||||||
|
clearTimer() {
|
||||||
|
if (this.timerId) {
|
||||||
|
clearTimeout(this.timerId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
startTimer() {
|
||||||
|
this.clearTimer();
|
||||||
|
this.timerId = setTimeout(() => {
|
||||||
|
this.props.dismiss();
|
||||||
|
}, this.props.duration || 5000);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div className="notification">
|
||||||
|
<p className="notification__message" ref={c => this.message = c}>{this.props.message}</p>
|
||||||
|
<button
|
||||||
|
className="btn notification__button"
|
||||||
|
onClick={this.props.action}
|
||||||
|
ref={c => this.button = c}
|
||||||
|
type="button">{this.props.actionLabel}</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Notification;
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
@import 'views/styles/shared';
|
||||||
|
|
||||||
|
|
||||||
|
.notification {
|
||||||
|
@include clearfix;
|
||||||
|
position: fixed;
|
||||||
|
left: 50%;
|
||||||
|
top: 60px;
|
||||||
|
margin-left: -100px;
|
||||||
|
border: 1px solid #aaa;
|
||||||
|
padding: 10px 15px;
|
||||||
|
width: 200px;
|
||||||
|
font-size: rem(16px);
|
||||||
|
line-height: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification__message {
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification__button {
|
||||||
|
float: right;
|
||||||
|
font-size: rem(16px);
|
||||||
|
line-height: 24px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: #85bf6b;
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { default } from './require-auth-route';
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Route, Redirect } from 'react-router-dom'
|
||||||
|
|
||||||
|
//cc:signin#3;enable route;+6
|
||||||
|
const RequireAuthRoute = ({component: Component, authenticated, ...rest}) => (
|
||||||
|
<Route
|
||||||
|
{...rest}
|
||||||
|
render={props => {
|
||||||
|
return authenticated ? (
|
||||||
|
<Component {...props}/>
|
||||||
|
) : (
|
||||||
|
<Redirect to={{
|
||||||
|
pathname: '/sign-in',
|
||||||
|
state: {from: props.location}
|
||||||
|
}}/>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
export default RequireAuthRoute;
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { default } from './require-unauth-route';
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Route, Redirect } from 'react-router-dom'
|
||||||
|
|
||||||
|
|
||||||
|
const RequireUnauthRoute = ({component: Component, authenticated, ...rest}) => (
|
||||||
|
<Route
|
||||||
|
{...rest}
|
||||||
|
render={props => {
|
||||||
|
return authenticated ? (
|
||||||
|
<Redirect to={{
|
||||||
|
pathname: '/',
|
||||||
|
state: {from: props.location}
|
||||||
|
}}/>
|
||||||
|
) : (
|
||||||
|
<Component {...props}/>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
export default RequireUnauthRoute;
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { default } from './task-filters';
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { NavLink } from 'react-router-dom';
|
||||||
|
|
||||||
|
import './task-filters.css';
|
||||||
|
|
||||||
|
|
||||||
|
const TaskFilters = ({filter}) => (
|
||||||
|
<ul className="task-filters">
|
||||||
|
<li><NavLink isActive={() => !filter} to="/">View All</NavLink></li>
|
||||||
|
<li><NavLink isActive={() => filter === 'active'} to={{pathname: '/', search: '?filter=active'}}>Active</NavLink></li>
|
||||||
|
<li><NavLink isActive={() => filter === 'completed'} to={{pathname: '/', search: '?filter=completed'}}>Completed</NavLink></li>
|
||||||
|
</ul>
|
||||||
|
);
|
||||||
|
|
||||||
|
TaskFilters.propTypes = {
|
||||||
|
filter: PropTypes.string
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export default TaskFilters;
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
@import 'views/styles/shared';
|
||||||
|
|
||||||
|
|
||||||
|
.task-filters {
|
||||||
|
@include clearfix;
|
||||||
|
margin-bottom: 45px;
|
||||||
|
padding-left: 1px;
|
||||||
|
font-size: rem(16px);
|
||||||
|
line-height: 24px;
|
||||||
|
list-style-type: none;
|
||||||
|
|
||||||
|
@include media-query(540) {
|
||||||
|
margin-bottom: 55px;
|
||||||
|
}
|
||||||
|
|
||||||
|
li {
|
||||||
|
float: left;
|
||||||
|
|
||||||
|
&:not(:first-child) {
|
||||||
|
margin-left: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:not(:first-child):before {
|
||||||
|
padding-right: 12px;
|
||||||
|
content: '/';
|
||||||
|
font-weight: 300;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: #999;
|
||||||
|
text-decoration: none;
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
1
example-project/src/views/components/task-form/index.js
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export { default } from './task-form';
|
||||||
62
example-project/src/views/components/task-form/task-form.js
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
import React, { Component } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
import './task-form.css';
|
||||||
|
|
||||||
|
|
||||||
|
export class TaskForm extends Component {
|
||||||
|
static propTypes = {
|
||||||
|
handleSubmit: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super(...arguments);
|
||||||
|
|
||||||
|
this.state = {title: ''};
|
||||||
|
|
||||||
|
this.handleChange = this.handleChange.bind(this);
|
||||||
|
this.handleKeyUp = this.handleKeyUp.bind(this);
|
||||||
|
this.handleSubmit = this.handleSubmit.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
clearInput() {
|
||||||
|
this.setState({title: ''});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleChange(event) {
|
||||||
|
this.setState({title: event.target.value});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleKeyUp(event) {
|
||||||
|
if (event.keyCode === 27) this.clearInput();
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSubmit(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
const title = this.state.title.trim();
|
||||||
|
if (title.length) this.props.handleSubmit(title);
|
||||||
|
this.clearInput();
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<form className="task-form" onSubmit={this.handleSubmit} noValidate>
|
||||||
|
<input
|
||||||
|
autoComplete="off"
|
||||||
|
autoFocus
|
||||||
|
className="task-form__input"
|
||||||
|
maxLength="64"
|
||||||
|
onChange={this.handleChange}
|
||||||
|
onKeyUp={this.handleKeyUp}
|
||||||
|
placeholder="What needs to be done?"
|
||||||
|
ref={e => this.titleInput = e}
|
||||||
|
type="text"
|
||||||
|
value={this.state.title}
|
||||||
|
/>
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export default TaskForm;
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
@import 'views/styles/shared';
|
||||||
|
|
||||||
|
|
||||||
|
.task-form {
|
||||||
|
margin: 40px 0 10px;
|
||||||
|
|
||||||
|
@include media-query(540) {
|
||||||
|
margin: 80px 0 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-form__input {
|
||||||
|
outline: none;
|
||||||
|
border: 0;
|
||||||
|
border-bottom: 1px dotted #666;
|
||||||
|
border-radius: 0;
|
||||||
|
padding: 0 0 5px 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 50px;
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: rem(24px);
|
||||||
|
font-weight: 300;
|
||||||
|
color: #fff;
|
||||||
|
background: transparent;
|
||||||
|
|
||||||
|
@include media-query(540) {
|
||||||
|
height: 61px;
|
||||||
|
font-size: rem(32px);
|
||||||
|
}
|
||||||
|
|
||||||
|
&::placeholder {
|
||||||
|
color: #999;
|
||||||
|
opacity: 1; // firefox native placeholder style has opacity < 1
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus::placeholder {
|
||||||
|
color: #777;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// webkit input doesn't inherit font-smoothing from ancestors
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
|
||||||
|
// remove `x`
|
||||||
|
&::-ms-clear {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
1
example-project/src/views/components/task-item/index.js
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export { default } from './task-item';
|
||||||
137
example-project/src/views/components/task-item/task-item.js
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
import React, { Component } from 'react';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import Button from '../button';
|
||||||
|
import Icon from '../icon';
|
||||||
|
|
||||||
|
import './task-item.css';
|
||||||
|
|
||||||
|
//cc:there is task
|
||||||
|
export class TaskItem extends Component {
|
||||||
|
constructor() {
|
||||||
|
super(...arguments);
|
||||||
|
|
||||||
|
this.state = {editing: false};
|
||||||
|
|
||||||
|
this.edit = this.edit.bind(this);
|
||||||
|
this.handleKeyUp = this.handleKeyUp.bind(this);
|
||||||
|
this.remove = this.remove.bind(this);
|
||||||
|
this.save = this.save.bind(this);
|
||||||
|
this.stopEditing = this.stopEditing.bind(this);
|
||||||
|
this.toggleStatus = this.toggleStatus.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
edit() {
|
||||||
|
this.setState({editing: true});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleKeyUp(event) {
|
||||||
|
if (event.keyCode === 13) {
|
||||||
|
this.save(event);
|
||||||
|
}
|
||||||
|
else if (event.keyCode === 27) {
|
||||||
|
this.stopEditing();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
remove() {
|
||||||
|
this.props.removeTask(this.props.task);
|
||||||
|
}
|
||||||
|
|
||||||
|
save(event) {
|
||||||
|
if (this.state.editing) {
|
||||||
|
const { task } = this.props;
|
||||||
|
const title = event.target.value.trim();
|
||||||
|
|
||||||
|
if (title.length && title !== task.title) {
|
||||||
|
this.props.updateTask(task, {title});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.stopEditing();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stopEditing() {
|
||||||
|
this.setState({editing: false});
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleStatus() {
|
||||||
|
const { task } = this.props;
|
||||||
|
this.props.updateTask(task, {completed: !task.completed});
|
||||||
|
}
|
||||||
|
|
||||||
|
renderTitle(task) {
|
||||||
|
return (
|
||||||
|
<div className="task-item__title" tabIndex="0">
|
||||||
|
{task.title}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderTitleInput(task) {
|
||||||
|
return (
|
||||||
|
<input
|
||||||
|
autoComplete="off"
|
||||||
|
autoFocus
|
||||||
|
className="task-item__input"
|
||||||
|
defaultValue={task.title}
|
||||||
|
maxLength="64"
|
||||||
|
onKeyUp={this.handleKeyUp}
|
||||||
|
type="text"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { editing } = this.state;
|
||||||
|
const { task } = this.props;
|
||||||
|
|
||||||
|
let containerClasses = classNames('task-item', {
|
||||||
|
'task-item--completed': task.completed,
|
||||||
|
'task-item--editing': editing
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={containerClasses} tabIndex="0">
|
||||||
|
<div className="cell">
|
||||||
|
<Button
|
||||||
|
className={classNames('btn--icon', 'task-item__button', {'active': task.completed, 'hide': editing})}
|
||||||
|
onClick={this.toggleStatus}>
|
||||||
|
<Icon name="done" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="cell">
|
||||||
|
{editing ? this.renderTitleInput(task) : this.renderTitle(task)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="cell">
|
||||||
|
<Button
|
||||||
|
className={classNames('btn--icon', 'task-item__button', {'hide': editing})}
|
||||||
|
onClick={this.edit}>
|
||||||
|
<Icon name="mode_edit" />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
className={classNames('btn--icon', 'task-item__button', {'hide': !editing})}
|
||||||
|
onClick={this.stopEditing}>
|
||||||
|
<Icon name="clear" />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
className={classNames('btn--icon', 'task-item__button', {'hide': editing})}
|
||||||
|
onClick={this.remove}>
|
||||||
|
<Icon name="delete" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TaskItem.propTypes = {
|
||||||
|
removeTask: PropTypes.func.isRequired,
|
||||||
|
task: PropTypes.object.isRequired,
|
||||||
|
updateTask: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export default TaskItem;
|
||||||
124
example-project/src/views/components/task-item/task-item.scss
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
@import 'views/styles/shared';
|
||||||
|
|
||||||
|
|
||||||
|
.task-item {
|
||||||
|
display: flex;
|
||||||
|
outline: none;
|
||||||
|
border-bottom: 1px dotted #666;
|
||||||
|
height: 60px;
|
||||||
|
overflow: hidden;
|
||||||
|
color: #fff;
|
||||||
|
font-size: rem(18px);
|
||||||
|
font-weight: 300;
|
||||||
|
|
||||||
|
@include media-query(540) {
|
||||||
|
font-size: rem(24px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-item--editing {
|
||||||
|
border-bottom: 1px dotted #ccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//=====================================
|
||||||
|
// Cells
|
||||||
|
//-------------------------------------
|
||||||
|
.cell {
|
||||||
|
&:first-child,
|
||||||
|
&:last-child {
|
||||||
|
display: flex;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
padding-right: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:nth-child(2) {
|
||||||
|
flex: 1;
|
||||||
|
padding-right: 30px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//=====================================
|
||||||
|
// Buttons
|
||||||
|
//-------------------------------------
|
||||||
|
.task-item__button {
|
||||||
|
margin-left: 5px;
|
||||||
|
background: #2a2a2a;
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
color: #555;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
background: #262626;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
color: #85bf6b;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//=====================================
|
||||||
|
// Title (static)
|
||||||
|
//-------------------------------------
|
||||||
|
.task-item__title {
|
||||||
|
display: inline-block;
|
||||||
|
position: relative;
|
||||||
|
max-width: 100%;
|
||||||
|
line-height: 60px;
|
||||||
|
outline: none;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
|
&:after {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
bottom: 0;
|
||||||
|
border-top: 2px solid #85bf6b;
|
||||||
|
width: 0;
|
||||||
|
height: 46%;
|
||||||
|
content: '';
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-item--completed & {
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-item--completed &:after {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//=====================================
|
||||||
|
// Title (input)
|
||||||
|
//-------------------------------------
|
||||||
|
.task-item__input {
|
||||||
|
outline: none;
|
||||||
|
border: 0;
|
||||||
|
padding: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 60px;
|
||||||
|
color: inherit;
|
||||||
|
font: inherit;
|
||||||
|
background: transparent;
|
||||||
|
|
||||||
|
// hide `x`
|
||||||
|
&::-ms-clear {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
1
example-project/src/views/components/task-list/index.js
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export { default } from './task-list';
|
||||||
32
example-project/src/views/components/task-list/task-list.js
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { List } from 'immutable';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import TaskItem from '../task-item/task-item';
|
||||||
|
|
||||||
|
//cc:layout#3;tasks list;
|
||||||
|
function TaskList({removeTask, tasks, updateTask}) {
|
||||||
|
let taskItems = tasks.map((task, index) => {
|
||||||
|
return (
|
||||||
|
<TaskItem
|
||||||
|
key={index}
|
||||||
|
task={task}
|
||||||
|
removeTask={removeTask}
|
||||||
|
updateTask={updateTask}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="task-list">
|
||||||
|
{taskItems}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
TaskList.propTypes = {
|
||||||
|
removeTask: PropTypes.func.isRequired,
|
||||||
|
tasks: PropTypes.instanceOf(List).isRequired,
|
||||||
|
updateTask: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TaskList;
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
@import 'views/styles/shared';
|
||||||
|
|
||||||
|
|
||||||
|
.task-list {
|
||||||
|
border-top: 1px dotted #666;
|
||||||
|
}
|
||||||
1
example-project/src/views/pages/sign-in/index.js
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export { default } from './sign-in-page';
|
||||||
46
example-project/src/views/pages/sign-in/sign-in-page.js
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { withRouter } from 'react-router-dom';
|
||||||
|
import { authActions } from '../../../auth';
|
||||||
|
import Button from '../../../views/components/button';
|
||||||
|
|
||||||
|
import './sign-in-page.css';
|
||||||
|
|
||||||
|
|
||||||
|
const SignInPage = ({signInWithGithub, signInWithGoogle, signInWithTwitter}) => {
|
||||||
|
return (
|
||||||
|
<div className="g-row sign-in">
|
||||||
|
<div className="g-col">
|
||||||
|
<h1 className="sign-in__heading">Sign in</h1>
|
||||||
|
<Button className="sign-in__button" onClick={signInWithGithub}>GitHub</Button>
|
||||||
|
<Button className="sign-in__button" onClick={signInWithGoogle}>Google</Button>
|
||||||
|
<Button className="sign-in__button" onClick={signInWithTwitter}>Twitter</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
SignInPage.propTypes = {
|
||||||
|
signInWithGithub: PropTypes.func.isRequired,
|
||||||
|
signInWithGoogle: PropTypes.func.isRequired,
|
||||||
|
signInWithTwitter: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
//=====================================
|
||||||
|
// CONNECT
|
||||||
|
//-------------------------------------
|
||||||
|
|
||||||
|
const mapDispatchToProps = {
|
||||||
|
signInWithGithub: authActions.signInWithGithub, //cc:signin#0;dispatch action
|
||||||
|
signInWithGoogle: authActions.signInWithGoogle,
|
||||||
|
signInWithTwitter: authActions.signInWithTwitter
|
||||||
|
};
|
||||||
|
|
||||||
|
export default withRouter(
|
||||||
|
connect(
|
||||||
|
null,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(SignInPage)
|
||||||
|
);
|
||||||
30
example-project/src/views/pages/sign-in/sign-in-page.scss
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
@import 'views/styles/shared';
|
||||||
|
|
||||||
|
|
||||||
|
.sign-in {
|
||||||
|
margin-top: 90px;
|
||||||
|
max-width: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sign-in__heading {
|
||||||
|
margin-bottom: 36px;
|
||||||
|
font-size: 30px;
|
||||||
|
font-weight: 300;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sign-in__button {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
border: 1px solid #555;
|
||||||
|
width: 100%;
|
||||||
|
height: 48px;
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: rem(18px);
|
||||||
|
line-height: 48px;
|
||||||
|
color: #999;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border: 2px solid #aaa;
|
||||||
|
line-height: 46px;
|
||||||
|
}
|
||||||
|
}
|
||||||
1
example-project/src/views/pages/tasks/index.js
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export { default } from './tasks-page';
|
||||||
115
example-project/src/views/pages/tasks/tasks-page.js
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
import React, { Component } from 'react';
|
||||||
|
import { List } from 'immutable';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { createSelector } from 'reselect';
|
||||||
|
|
||||||
|
import { getNotification, notificationActions } from '../../../notification';
|
||||||
|
import { getTaskFilter, getVisibleTasks, tasksActions } from '../../../tasks';
|
||||||
|
import Notification from '../../components/notification';
|
||||||
|
import TaskFilters from '../../components/task-filters';
|
||||||
|
import TaskForm from '../../components/task-form';
|
||||||
|
import TaskList from '../../components/task-list';
|
||||||
|
|
||||||
|
//cc:layout#2;tasks page
|
||||||
|
export class TasksPage extends Component {
|
||||||
|
static propTypes = {
|
||||||
|
createTask: PropTypes.func.isRequired,
|
||||||
|
dismissNotification: PropTypes.func.isRequired,
|
||||||
|
filterTasks: PropTypes.func.isRequired,
|
||||||
|
filterType: PropTypes.string.isRequired,
|
||||||
|
loadTasks: PropTypes.func.isRequired,
|
||||||
|
location: PropTypes.object.isRequired,
|
||||||
|
notification: PropTypes.object.isRequired,
|
||||||
|
removeTask: PropTypes.func.isRequired,
|
||||||
|
tasks: PropTypes.instanceOf(List).isRequired,
|
||||||
|
undeleteTask: PropTypes.func.isRequired,
|
||||||
|
unloadTasks: PropTypes.func.isRequired,
|
||||||
|
updateTask: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
componentWillMount() {
|
||||||
|
this.props.loadTasks();
|
||||||
|
this.props.filterTasks(
|
||||||
|
this.getFilterParam(this.props.location.search)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillReceiveProps(nextProps) {
|
||||||
|
if (nextProps.location.search !== this.props.location.search) {
|
||||||
|
this.props.filterTasks(
|
||||||
|
this.getFilterParam(nextProps.location.search)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
this.props.unloadTasks();
|
||||||
|
}
|
||||||
|
|
||||||
|
getFilterParam(search) {
|
||||||
|
const params = new URLSearchParams(search);
|
||||||
|
return params.get('filter');
|
||||||
|
}
|
||||||
|
|
||||||
|
renderNotification() {
|
||||||
|
const { notification } = this.props;
|
||||||
|
return (
|
||||||
|
<Notification
|
||||||
|
action={this.props.undeleteTask}
|
||||||
|
actionLabel={notification.actionLabel}
|
||||||
|
dismiss={this.props.dismissNotification}
|
||||||
|
display={notification.display}
|
||||||
|
message={notification.message}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div className="g-row">
|
||||||
|
<div className="g-col">
|
||||||
|
<TaskForm handleSubmit={this.props.createTask} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="g-col">
|
||||||
|
<TaskFilters filter={this.props.filterType} />
|
||||||
|
<TaskList
|
||||||
|
removeTask={this.props.removeTask}
|
||||||
|
tasks={this.props.tasks}
|
||||||
|
updateTask={this.props.updateTask}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{this.props.notification.display ? this.renderNotification() : null}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//=====================================
|
||||||
|
// CONNECT
|
||||||
|
//-------------------------------------
|
||||||
|
|
||||||
|
const mapStateToProps = createSelector(
|
||||||
|
getNotification,
|
||||||
|
getTaskFilter,
|
||||||
|
getVisibleTasks,
|
||||||
|
(notification, filterType, tasks) => ({
|
||||||
|
notification,
|
||||||
|
filterType,
|
||||||
|
tasks
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const mapDispatchToProps = Object.assign(
|
||||||
|
{},
|
||||||
|
tasksActions,
|
||||||
|
notificationActions
|
||||||
|
);
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(TasksPage);
|
||||||
8
example-project/src/views/styles/_grid.scss
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
.g-row {
|
||||||
|
@include grid-row;
|
||||||
|
}
|
||||||
|
|
||||||
|
.g-col {
|
||||||
|
@include grid-column;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
11
example-project/src/views/styles/_settings.scss
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
$base-background-color: #222 !default;
|
||||||
|
$base-font-color: #999 !default;
|
||||||
|
$base-font-family: 'aktiv-grotesk-std', Helvetica Neue, Arial, sans-serif !default;
|
||||||
|
$base-font-size: 18px !default;
|
||||||
|
$base-line-height: 24px !default;
|
||||||
|
|
||||||
|
|
||||||
|
//=====================================
|
||||||
|
// Grid
|
||||||
|
//-------------------------------------
|
||||||
|
$grid-max-width: 810px !default;
|
||||||
5
example-project/src/views/styles/_shared.scss
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
@import
|
||||||
|
'./settings',
|
||||||
|
'minx/src/settings',
|
||||||
|
'minx/src/functions',
|
||||||
|
'minx/src/mixins';
|
||||||
27
example-project/src/views/styles/styles.scss
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
@import
|
||||||
|
'./shared',
|
||||||
|
'minx/src/reset',
|
||||||
|
'minx/src/elements',
|
||||||
|
'./grid';
|
||||||
|
|
||||||
|
|
||||||
|
html {
|
||||||
|
overflow-y: scroll;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
padding-bottom: 120px;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hide {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
::selection {
|
||||||
|
background: rgba(200,200,255,.1);
|
||||||
|
}
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
/**
|
|
||||||
* Created by bliashenko on 2018-06-07.
|
|
||||||
*/
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
import stringFormat from '../../utils/string/format/string';
|
|
||||||
export default 'HOME_PAGE';
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
export default 'PRODUCT_PAGE';
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
import HomePage from './home/home-page';
|
|
||||||
import ProductPage from './product/product-page';
|
|
||||||
|
|
||||||
//codecrumb:tabsSwitch;details 1
|
|
||||||
const tabsSwitch = index => {
|
|
||||||
console.log(index);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default {
|
|
||||||
tabsSwitch,//codecrumb:setup call;details 2
|
|
||||||
render: () => {
|
|
||||||
return [HomePage, ProductPage];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
22
example-project/webpack.config.js
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
/* global __dirname, require, module*/
|
||||||
|
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
const outputFile = 'bundle.js';
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
entry: __dirname + '/src/index.js',
|
||||||
|
output: {
|
||||||
|
path: __dirname + '/dist',
|
||||||
|
filename: outputFile
|
||||||
|
},
|
||||||
|
module: {
|
||||||
|
rules: [ ]
|
||||||
|
},
|
||||||
|
resolve: {
|
||||||
|
extensions: ['.js']
|
||||||
|
},
|
||||||
|
mode: 'development'
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = config;
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
/**
|
|
||||||
* Created by bliashenko on 2018-06-07.
|
|
||||||
*/
|
|
||||||
87
package.json
@@ -1,57 +1,86 @@
|
|||||||
{
|
{
|
||||||
"name": "codecrumbs",
|
"name": "codecrumbs",
|
||||||
"version": "1.0.0",
|
"version": "1.0.12-alpha",
|
||||||
"main": "index.js",
|
|
||||||
"author": "Bohdan Liashenko",
|
"author": "Bohdan Liashenko",
|
||||||
"license": "MIT",
|
"license": "BSD-3-Clause",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/Bogdan-Lyashenko/codecrumbs.git"
|
||||||
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "yarn start:client & yarn start:server",
|
"start": "yarn client-dev & yarn server-dev",
|
||||||
"start:client": "cd src/public && webpack --progress --colors --watch --env dev",
|
"start:demo": "node src/index.dev.js",
|
||||||
"start:server": "nodemon ./src/server/index.js",
|
"client-dev": "cd src/public && webpack --config webpack.dev.js --progress --colors --watch --env dev",
|
||||||
"start:server-debug": "nodemon --inspect ./src/server/index.js",
|
"server-dev": "nodemon src/index.dev.js",
|
||||||
"start:demo": "node ./src/server/index.js",
|
"server-debug": "nodemon --inspect src/index.dev.js",
|
||||||
|
"build": "cd src/public && webpack --config webpack.prod.js --progress --colors",
|
||||||
|
"start:standalone": "cd src/public/dist/standalone && http-server",
|
||||||
"pretty": "prettier --write \"./src/public/js/**/*.js\""
|
"pretty": "prettier --write \"./src/public/js/**/*.js\""
|
||||||
},
|
},
|
||||||
|
"bin": {
|
||||||
|
"codecrumbs": "./cli/index.cli.js"
|
||||||
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"antd": "^3.5.0",
|
"@babel/parser": "^7.1.2",
|
||||||
"babel-core": "^6.26.3",
|
"@babel/polyfill": "^7.0.0",
|
||||||
"babel-generator": "^6.26.1",
|
"@babel/traverse": "^7.1.0",
|
||||||
"babel-loader": "^7.1.4",
|
"antd": "^3.9.2",
|
||||||
"babel-plugin-import": "^1.7.0",
|
|
||||||
"babel-plugin-transform-react-jsx": "^6.24.1",
|
|
||||||
"babel-polyfill": "^6.26.0",
|
|
||||||
"babel-traverse": "^6.26.0",
|
|
||||||
"babylon": "^6.18.0",
|
|
||||||
"chokidar": "^2.0.3",
|
"chokidar": "^2.0.3",
|
||||||
"css-loader": "^0.28.11",
|
"classnames": "^2.2.6",
|
||||||
|
"colors": "^1.3.2",
|
||||||
|
"commander": "^2.19.0",
|
||||||
|
"copy-text-to-clipboard": "^1.0.4",
|
||||||
"d3-flextree": "^2.1.1",
|
"d3-flextree": "^2.1.1",
|
||||||
"directory-tree": "^2.1.0",
|
"directory-tree": "^2.1.0",
|
||||||
|
"file-saver": "^2.0.0",
|
||||||
|
"http-server": "^0.11.1",
|
||||||
|
"js2flowchart": "^1.1.7",
|
||||||
"lodash": "^4.17.10",
|
"lodash": "^4.17.10",
|
||||||
"lodash.debounce": "^4.0.8",
|
"lodash.debounce": "^4.0.8",
|
||||||
"madge": "^3.0.1",
|
"madge": "^3.3.0",
|
||||||
"mime-types": "^2.1.18",
|
"portscanner": "^2.2.0",
|
||||||
"react": "^16.3.2",
|
"react": "^16.7.0",
|
||||||
"react-dom": "^16.3.2",
|
"react-dom": "^16.7.0",
|
||||||
|
"react-draggable": "^3.0.5",
|
||||||
"react-redux": "^5.0.7",
|
"react-redux": "^5.0.7",
|
||||||
"react-syntax-highlighter": "^7.0.4",
|
"react-syntax-highlighter": "8.0.1",
|
||||||
"redux": "^4.0.0",
|
"redux": "^4.0.0",
|
||||||
|
"redux-persist": "^5.10.0",
|
||||||
"redux-saga": "^0.16.0",
|
"redux-saga": "^0.16.0",
|
||||||
"redux-thunk": "^2.2.0",
|
"redux-thunk": "^2.2.0",
|
||||||
"style-loader": "^0.21.0",
|
"reselect": "^4.0.0",
|
||||||
"svg.js": "^2.6.4",
|
|
||||||
"webpack": "^4.6.0",
|
|
||||||
"websocket": "^1.0.26"
|
"websocket": "^1.0.26"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"babel-preset-stage-2": "^6.24.1",
|
"@babel/core": "^7.1.2",
|
||||||
"nodemon": "^1.17.3",
|
"@babel/plugin-syntax-dynamic-import": "^7.2.0",
|
||||||
|
"@babel/preset-env": "^7.1.0",
|
||||||
|
"@babel/preset-react": "^7.0.0",
|
||||||
|
"@commitlint/cli": "^7.3.2",
|
||||||
|
"@commitlint/config-conventional": "^7.3.1",
|
||||||
|
"babel-loader": "^8.0.4",
|
||||||
|
"babel-plugin-import": "^1.9.1",
|
||||||
|
"css-loader": "^0.28.11",
|
||||||
|
"husky": "^1.3.1",
|
||||||
|
"node-sass": "^4.9.3",
|
||||||
|
"nodemon": "^1.18.7",
|
||||||
"prettier": "^1.14.0",
|
"prettier": "^1.14.0",
|
||||||
"webpack-cli": "^2.1.2"
|
"sass-loader": "^7.1.0",
|
||||||
|
"style-loader": "^0.21.0",
|
||||||
|
"webpack": "^4.20.2",
|
||||||
|
"webpack-bundle-analyzer": "^3.0.3",
|
||||||
|
"webpack-cli": "^3.1.2",
|
||||||
|
"webpack-merge": "^4.1.5"
|
||||||
},
|
},
|
||||||
"nodemonConfig": {
|
"nodemonConfig": {
|
||||||
"ignore": [
|
"ignore": [
|
||||||
"src/public/**/*.*"
|
"src/public/**/*.*"
|
||||||
],
|
],
|
||||||
"delay": "2500"
|
"delay": "2500"
|
||||||
|
},
|
||||||
|
"husky": {
|
||||||
|
"hooks": {
|
||||||
|
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
10
src/index.dev.js
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
const server = require('../src/server');
|
||||||
|
|
||||||
|
const projectNameAlias = 'react-todo-example';
|
||||||
|
const projectDir = `example-project/src`;
|
||||||
|
const entryPoint = `example-project/src/index.js`;
|
||||||
|
const webpackConfigPath = `example-project/webpack.config.js`;
|
||||||
|
const clientPort = 2018;
|
||||||
|
|
||||||
|
const isDev = true;
|
||||||
|
server.setup({ projectNameAlias, projectDir, entryPoint, webpackConfigPath, clientPort }, isDev);
|
||||||
88459
src/public/dist/bundle.js
vendored
1
src/public/dist/bundle.js.map
vendored
11
src/public/dist/index.html
vendored
@@ -1,11 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<title>Codecrumbs!</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="mount-node" style="height: 100%"></div>
|
|
||||||
<script src="bundle.js" type="text/javascript"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
48908
src/public/dist/local/bundle/main.bundle.js
vendored
Normal file
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
21
src/public/dist/local/index.html
vendored
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>Codecrumbs: better way to navigate the code maze!</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="mount-node" style="height: 100%"></div>
|
||||||
|
<script src="./bundle/main.bundle.js" type="text/javascript"></script>
|
||||||
|
<script type="text/javascript">
|
||||||
|
(function(window) {
|
||||||
|
if (!window.codecrumbs) {
|
||||||
|
console.error('Codecrumbs project is not loaded!');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
window.codecrumbs.default({}, 'mount-node');
|
||||||
|
})(window)
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="iso-8859-1"?>
|
|
||||||
<!-- Generator: Adobe Illustrator 17.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
|
||||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Capa_1" x="0px" y="0px" viewBox="0 0 233.741 233.741" style="enable-background:new 0 0 233.741 233.741;" xml:space="preserve" width="512px" height="512px">
|
|
||||||
<path d="M233.486,83.462V54.886c0-10.201-8.299-18.5-18.5-18.5h-85.322c-3.63,0-9.295-2.876-11.437-5.806 l-6.386-8.736C106.859,15.03,96.736,9.892,88.296,9.892H58.731c-9.295,0-18.64,6.608-21.738,15.371l-2.034,5.753 c-0.958,2.711-4.72,5.37-7.595,5.37H18.5c-10.201,0-18.5,8.299-18.5,18.5v41.456c0,0.661,0.095,1.299,0.255,1.91V201.35 c0,12.407,10.094,22.5,22.5,22.5h188.486c12.406,0,22.5-10.093,22.5-22.5V86.847C233.741,85.696,233.653,84.566,233.486,83.462z M18.5,51.386h8.864c9.296,0,18.641-6.608,21.737-15.371l2.034-5.752c0.958-2.711,4.721-5.371,7.596-5.371h29.564 c3.63,0,9.295,2.877,11.438,5.806l6.385,8.735c4.982,6.815,15.104,11.954,23.546,11.954h85.322c1.898,0,3.5,1.604,3.5,3.5V65.55 c-2.275-0.776-4.71-1.204-7.245-1.204H22.755c-2.725,0-5.336,0.487-7.755,1.378V54.886C15,52.989,16.603,51.386,18.5,51.386z M211.241,208.85H22.755c-4.136,0-7.5-3.365-7.5-7.5V86.847c0-4.135,3.364-7.5,7.5-7.5h188.486c3.477,0,6.398,2.38,7.245,5.594 v3.915c0,0.661,0.095,1.3,0.255,1.91V201.35C218.741,205.485,215.377,208.85,211.241,208.85z" fill="#ccc"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 1.5 KiB |
6
src/public/dist/resources/closed-folder.svg
vendored
@@ -1,6 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="iso-8859-1"?>
|
|
||||||
<!-- Generator: Adobe Illustrator 17.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
|
||||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Capa_1" x="0px" y="0px" viewBox="0 0 233.741 233.741" style="enable-background:new 0 0 233.741 233.741;" xml:space="preserve" width="512px" height="512px">
|
|
||||||
<path d="M233.486,83.462V54.886c0-10.201-8.299-18.5-18.5-18.5h-85.322c-3.63,0-9.295-2.876-11.437-5.806 l-6.386-8.736C106.859,15.03,96.736,9.892,88.296,9.892H58.731c-9.295,0-18.64,6.608-21.738,15.371l-2.034,5.753 c-0.958,2.711-4.72,5.37-7.595,5.37H18.5c-10.201,0-18.5,8.299-18.5,18.5v41.456c0,0.661,0.095,1.299,0.255,1.91V201.35 c0,12.407,10.094,22.5,22.5,22.5h188.486c12.406,0,22.5-10.093,22.5-22.5V86.847C233.741,85.696,233.653,84.566,233.486,83.462z M18.5,51.386h8.864c9.296,0,18.641-6.608,21.737-15.371l2.034-5.752c0.958-2.711,4.721-5.371,7.596-5.371h29.564 c3.63,0,9.295,2.877,11.438,5.806l6.385,8.735c4.982,6.815,15.104,11.954,23.546,11.954h85.322c1.898,0,3.5,1.604,3.5,3.5V65.55 c-2.275-0.776-4.71-1.204-7.245-1.204H22.755c-2.725,0-5.336,0.487-7.755,1.378V54.886C15,52.989,16.603,51.386,18.5,51.386z M211.241,208.85H22.755c-4.136,0-7.5-3.365-7.5-7.5V86.847c0-4.135,3.364-7.5,7.5-7.5h188.486c3.477,0,6.398,2.38,7.245,5.594 v3.915c0,0.661,0.095,1.3,0.255,1.91V201.35C218.741,205.485,215.377,208.85,211.241,208.85z" fill="#1890ff"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 1.5 KiB |
11
src/public/dist/resources/folder-disabled.svg
vendored
@@ -1,11 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="iso-8859-1"?>
|
|
||||||
<!-- Generator: Adobe Illustrator 17.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
|
||||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Capa_1" x="0px" y="0px" viewBox="0 0 276.157 276.157" style="enable-background:new 0 0 276.157 276.157;" xml:space="preserve" width="512px" height="512px">
|
|
||||||
<g>
|
|
||||||
<rect x="50" width="270" height="270" style="fill:white;" opacity="0.4"/>
|
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
<path d="M273.081,101.378c-3.3-4.651-8.86-7.319-15.255-7.319h-24.34v-26.47c0-10.201-8.299-18.5-18.5-18.5 h-85.322c-3.63,0-9.295-2.876-11.436-5.806l-6.386-8.735c-4.982-6.814-15.104-11.954-23.546-11.954H58.731 c-9.293,0-18.639,6.608-21.738,15.372l-2.033,5.752c-0.958,2.71-4.721,5.371-7.596,5.371H18.5c-10.201,0-18.5,8.299-18.5,18.5 v167.07c0,0.885,0.161,1.73,0.443,2.519c0.152,3.306,1.18,6.424,3.053,9.064c3.3,4.652,8.86,7.319,15.255,7.319h188.486 c11.395,0,23.27-8.424,27.035-19.179l40.677-116.188C277.061,112.159,276.381,106.03,273.081,101.378z M18.5,64.089h8.864 c9.295,0,18.64-6.608,21.738-15.372l2.032-5.75c0.959-2.711,4.722-5.372,7.597-5.372h29.564c3.63,0,9.295,2.876,11.437,5.806 l6.386,8.734c4.982,6.815,15.104,11.954,23.546,11.954h85.322c1.898,0,3.5,1.603,3.5,3.5v26.47H69.34 c-11.395,0-23.27,8.424-27.035,19.179L15,191.231V67.589C15,65.692,16.603,64.089,18.5,64.089z M260.791,113.238l-40.677,116.188 c-1.674,4.781-7.812,9.135-12.877,9.135H18.751c-1.448,0-2.577-0.373-3.02-0.998c-0.443-0.625-0.423-1.814,0.056-3.181 l40.677-116.188c1.674-4.781,7.812-9.135,12.877-9.135h188.486c1.448,0,2.577,0.373,3.021,0.998 C261.29,110.682,261.27,111.871,260.791,113.238z" fill="#ccc"/>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 1.8 KiB |
11
src/public/dist/resources/folder.svg
vendored
@@ -1,11 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="iso-8859-1"?>
|
|
||||||
<!-- Generator: Adobe Illustrator 17.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
|
||||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Capa_1" x="0px" y="0px" viewBox="0 0 276.157 276.157" style="enable-background:new 0 0 276.157 276.157;" xml:space="preserve" width="512px" height="512px">
|
|
||||||
<g>
|
|
||||||
<rect x="50" width="270" height="270" style="fill:white;" opacity="0.4"/>
|
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
<path d="M273.081,101.378c-3.3-4.651-8.86-7.319-15.255-7.319h-24.34v-26.47c0-10.201-8.299-18.5-18.5-18.5 h-85.322c-3.63,0-9.295-2.876-11.436-5.806l-6.386-8.735c-4.982-6.814-15.104-11.954-23.546-11.954H58.731 c-9.293,0-18.639,6.608-21.738,15.372l-2.033,5.752c-0.958,2.71-4.721,5.371-7.596,5.371H18.5c-10.201,0-18.5,8.299-18.5,18.5 v167.07c0,0.885,0.161,1.73,0.443,2.519c0.152,3.306,1.18,6.424,3.053,9.064c3.3,4.652,8.86,7.319,15.255,7.319h188.486 c11.395,0,23.27-8.424,27.035-19.179l40.677-116.188C277.061,112.159,276.381,106.03,273.081,101.378z M18.5,64.089h8.864 c9.295,0,18.64-6.608,21.738-15.372l2.032-5.75c0.959-2.711,4.722-5.372,7.597-5.372h29.564c3.63,0,9.295,2.876,11.437,5.806 l6.386,8.734c4.982,6.815,15.104,11.954,23.546,11.954h85.322c1.898,0,3.5,1.603,3.5,3.5v26.47H69.34 c-11.395,0-23.27,8.424-27.035,19.179L15,191.231V67.589C15,65.692,16.603,64.089,18.5,64.089z M260.791,113.238l-40.677,116.188 c-1.674,4.781-7.812,9.135-12.877,9.135H18.751c-1.448,0-2.577-0.373-3.02-0.998c-0.443-0.625-0.423-1.814,0.056-3.181 l40.677-116.188c1.674-4.781,7.812-9.135,12.877-9.135h188.486c1.448,0,2.577,0.373,3.021,0.998 C261.29,110.682,261.27,111.871,260.791,113.238z" fill="#1890ff"/>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 1.8 KiB |
12
src/public/dist/resources/js-file.svg
vendored
@@ -1,12 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="iso-8859-1"?>
|
|
||||||
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
|
||||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Capa_1" x="0px" y="0px" width="512px" height="512px" viewBox="0 0 550.801 550.801" style="enable-background:#" xml:space="preserve">
|
|
||||||
<g>
|
|
||||||
<rect x="50" width="412" height="512" style="fill:white;" opacity="0.7" />
|
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
<path d="M475.084,131.997c-0.021-2.531-0.833-5.021-2.567-6.992L366.325,3.694c-0.032-0.034-0.063-0.045-0.085-0.076 c-0.633-0.707-1.371-1.295-2.151-1.804c-0.231-0.155-0.464-0.285-0.706-0.422c-0.676-0.366-1.393-0.675-2.131-0.896 c-0.2-0.053-0.38-0.135-0.58-0.188C359.87,0.119,359.037,0,358.193,0H97.2c-11.918,0-21.6,9.693-21.6,21.601v507.6 c0,11.907,9.682,21.601,21.6,21.601h356.4c11.907,0,21.6-9.693,21.6-21.601V133.202C475.2,132.796,475.137,132.398,475.084,131.997 z M204.051,521.143c-6.94,0-16.055-1.182-21.998-3.164l3.375-24.374c4.158,1.393,9.503,2.384,15.448,2.384 c12.68,0,20.596-5.759,20.596-26.557v-83.996h30.122v84.396h0.005C251.599,507.875,233.374,521.143,204.051,521.143z M309.445,520.942c-15.25,0-30.29-3.966-37.823-8.121l6.138-24.965c8.124,4.166,20.606,8.321,33.489,8.321 c13.864,0,21.188-5.731,21.188-14.459c0-8.322-6.338-13.078-22.386-18.816c-22.185-7.73-36.647-20.007-36.647-39.424 c0-22.781,19.014-40.215,50.512-40.215c15.051,0,26.156,3.164,34.077,6.739l-6.719,24.363c-5.357-2.573-14.86-6.339-27.949-6.339 c-13.067,0-19.417,5.959-19.417,12.878c0,8.511,7.541,12.287,24.775,18.815c23.562,8.723,34.667,20.999,34.667,39.814 C363.34,501.926,346.096,520.942,309.445,520.942z M453.601,366.747H97.2V21.601h250.193v110.51c0,5.967,4.841,10.8,10.8,10.8 h95.407V366.747z M357.592,230.798c2.489,5.16,3.597,10.383,3.597,17.062c0,10.465-3.296,18.808-9.987,25.262 c-6.281,6.097-14.392,9.683-25.202,11.201c-4.303,0.591-12.725,0.622-17.218,0c-17.181-2.3-29.893-10.32-38.024-24.021 c-1.041-1.706-1.252-2.268-0.988-2.511c0.427-0.4,20.788-12.145,21.03-12.145c0.11,0,0.886,1.013,1.74,2.247 c6.26,9.255,14.049,13.537,24.474,13.537c6.187,0,11.153-1.685,14.308-4.872c0.943-0.934,1.982-2.326,2.383-3.154 c2.057-4.282,1.15-9.869-2.114-13.374c-2.647-2.837-6.798-5.115-17.951-9.898c-8.189-3.533-12.925-5.806-16.4-7.894 c-13.663-8.269-19.802-18.702-19.802-33.634c0-9.977,3.106-17.84,9.604-24.286c6.322-6.26,14.803-9.898,24.906-10.697 c3.828-0.298,4.366-0.298,8.433,0.026c8.295,0.614,14.581,2.618,20.466,6.528c3.186,2.117,7.815,6.927,10.378,10.779 c1.213,1.819,2.199,3.37,2.199,3.48c0,0.185-19.454,12.762-20.14,13.06c-0.221,0.073-0.833-0.644-1.666-1.957 c-0.723-1.149-2.272-3.045-3.48-4.221c-3.396-3.43-6.739-4.764-11.907-4.764c-4.546-0.032-7.488,1.118-10.025,3.878 c-1.767,1.901-2.567,3.85-2.758,6.74c-0.211,3.639,0.78,6.555,3.101,9.044c2.437,2.592,6.134,4.627,17.149,9.358 c14.576,6.265,21.885,10.597,27.527,16.318C354.175,224.889,355.884,227.259,357.592,230.798z M241.861,154.958h13.112v47.429 c0,31.462-0.108,48.179-0.271,49.673c-1.846,14.792-8.822,24.743-20.73,29.563c-9.924,3.986-23.461,4.313-34.003,0.802 c-9.975-3.322-18.27-10.539-23.058-20.092l-0.804-1.604l10.568-6.423c5.804-3.499,10.647-6.413,10.75-6.468 c0.087-0.032,0.783,1.015,1.556,2.302c4.786,8.189,8.904,11.101,15.783,11.074c4.817-0.026,7.702-0.968,9.954-3.214 c1.603-1.604,2.516-3.341,3.288-6.257l0.588-2.22l0.082-47.297l0.108-47.268H241.861z" fill="#1890ff"/>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
|
|
||||||
|
Before Width: | Height: | Size: 3.5 KiB |