Compare commits
No commits in common. "master" and "before-refactoring" have entirely different histories.
master
...
before-ref
|
|
@ -1,8 +0,0 @@
|
||||||
root = true
|
|
||||||
|
|
||||||
[*]
|
|
||||||
indent_style = space
|
|
||||||
indent_size = 2
|
|
||||||
charset = utf-8
|
|
||||||
trim_trailing_whitespace = true
|
|
||||||
insert_final_newline = true
|
|
||||||
|
|
@ -1,15 +1,6 @@
|
||||||
# Dependency directory
|
# Dependency directory
|
||||||
node_modules
|
node_modules
|
||||||
|
|
||||||
# Build directories
|
|
||||||
src/public/dist/local/bundle/
|
|
||||||
build/
|
|
||||||
|
|
||||||
# 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
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
.idea
|
|
||||||
example-project
|
|
||||||
docs
|
|
||||||
src/public/js
|
|
||||||
build
|
|
||||||
11
Dockerfile
|
|
@ -1,11 +0,0 @@
|
||||||
FROM node:14-slim
|
|
||||||
|
|
||||||
WORKDIR /usr/src/codecrumbs
|
|
||||||
|
|
||||||
COPY package*.json ./
|
|
||||||
|
|
||||||
RUN yarn install
|
|
||||||
|
|
||||||
COPY . .
|
|
||||||
|
|
||||||
EXPOSE 2018 3018
|
|
||||||
42
LICENSE
|
|
@ -1,29 +1,21 @@
|
||||||
BSD 3-Clause License
|
MIT License
|
||||||
|
|
||||||
Copyright (c) 2019, Bohdan Liashenko
|
Copyright (c) 2018 Bohdan Liashenko
|
||||||
All rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
modification, are permitted provided that the following conditions are met:
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
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:
|
||||||
|
|
||||||
* Redistributions of source code must retain the above copyright notice, this
|
The above copyright notice and this permission notice shall be included in all
|
||||||
list of conditions and the following disclaimer.
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
* Redistributions in binary form must reproduce the above copyright notice,
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
this list of conditions and the following disclaimer in the documentation
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
and/or other materials provided with the distribution.
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
* Neither the name of the copyright holder nor the names of its
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
contributors may be used to endorse or promote products derived from
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
this software without specific prior written permission.
|
SOFTWARE.
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
|
||||||
171
README.md
|
|
@ -1,169 +1,2 @@
|
||||||
[](https://badge.fury.io/js/codecrumbs) [](https://github.com/ellerbrock/open-source-badges/) [<img src="https://img.shields.io/twitter/follow/bliashenko.svg?label=Stay%20Tuned&style=social">](https://twitter.com/bliashenko)
|
# codecrumbs
|
||||||
|
Leave "breadcrumbs" in source code via comments to find your way out from code maze
|
||||||
<p align="center">
|
|
||||||
<img src="/docs/logo-sm.png" width="250"/>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<h3 align="center">
|
|
||||||
<a href="#what">What</a>
|
|
||||||
<span> · </span>
|
|
||||||
<a href="#demo">Demo</a>
|
|
||||||
<span> · </span>
|
|
||||||
<a href="#get-started">Get started</a>
|
|
||||||
<span> · </span>
|
|
||||||
<a href="#features">Features</a>
|
|
||||||
<span> · </span>
|
|
||||||
<a href="#case-studies">Case studies</a>
|
|
||||||
<span> · </span>
|
|
||||||
<a href="#support">Support</a>
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
**Have you ever got lost in a big or unknown codebase?** This tool will help you to solve that. Also, it will increase your development speed and give more knowledge about your application architecture.
|
|
||||||
> If you like this project, follow me on Twitter [@bliashenko](https://twitter.com/bliashenko) to hear about things I am building.
|
|
||||||
|
|
||||||
## Codecrumbs v2
|
|
||||||
Check out new version of this project as [standalone application](https://codecrumbs.io). Just in a few clicks you can start exploring a codebase in more efficient way, create interactive visual guides and share them with others on your own blog! See [quick guide here](https://codecrumbs.io/guides/web-app-with-github/).
|
|
||||||
|
|
||||||
<p align="center">
|
|
||||||
<a href="https://codecrumbs.io" target="_blank">
|
|
||||||
<img src="https://codecrumbs.io/external/img/common/app-ui-1.png" />
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
|
|
||||||
## Demo
|
|
||||||
Check out prepared example for [**standalone version running here**](https://codecrumbs.io/app).
|
|
||||||
|
|
||||||
## Codecrumbs v1
|
|
||||||
|
|
||||||
>**How it works?** You run `codecrumbs` command for a codebase, it analyzes source code and builds its visual representation. Write down a codecrumb-comment and codebase state will be reflected by visual client in browser on the fly.
|
|
||||||
>
|
|
||||||
> Check out [my talk at React-Finland](https://www.youtube.com/watch?v=S_1-1jzLxm4) for more details.
|
|
||||||
|
|
||||||
|
|
||||||
<img src="/docs/main-ui-3.png" width="100%"/>
|
|
||||||
|
|
||||||
## Get started
|
|
||||||
### Install and run
|
|
||||||
>Pre-condition: update/install `NodeJS` version to be >= *8.11.1*
|
|
||||||
|
|
||||||
1) Install ```codecrumbs``` globally (```yarn global add codecrumbs```)
|
|
||||||
2) Run ```codecrumbs -d project-src-dir -e project-src-dir/index.js```. Change parameters to match your project:```-d``` is *directory with source code*, ```-e``` is *entry point file* .
|
|
||||||
3) Go to [http://localhost:2018](http://localhost:2018/#) in the browser to check it out.
|
|
||||||
|
|
||||||
### Configuration
|
|
||||||
Run codecrumbs with CLI params or specify static config file `codecrumbs.config.js` (see example [here](/example-project/codecrumbs.config.js))
|
|
||||||
|
|
||||||
CLI | Config file | Description | Example
|
|
||||||
--- | --- | --- | ---
|
|
||||||
```d``` | ```projectDir``` | Relative path to project source code directory | ```-d src```
|
|
||||||
```e``` | ```entryPoint``` | Relative path to project source entry point file (must be inside ```dir```) | ```-e src/app.js```
|
|
||||||
```x``` | ```excludeDir``` | Relative path(or paths separated by ```,```) to directories for exclusion | ```-x src/doc,src/thirdparty```
|
|
||||||
```p``` | ```clientPort``` | Port for Codecrumbs client (optional, default *2018*) | ```-p 2019```
|
|
||||||
```n``` | ```projectNameAlias``` | Project name alias (optional, default same as ```-d``` value) | ```-n my-hello-world```
|
|
||||||
```C``` | - | Path to codecrumbs.config.js (optional, by default will try to find the file in PWD) | ```-C config/codecrumbs.config.js```
|
|
||||||
```D``` | ```debugModeEnabled``` | Enable debug mode for logs (optional, default is ```false```) | ```-D```
|
|
||||||
|
|
||||||
## Features
|
|
||||||
### Breadcrumbs and trails
|
|
||||||
|
|
||||||
<img src="/docs/cc-ui-3.png" width="750"/>
|
|
||||||
|
|
||||||
UI explained:
|
|
||||||
- enable "Codecrumbs" switch to have codecrumbs tree on the scheme (drop-down contains extra configuration)
|
|
||||||
- choose "current" codecrumbs trail to display (can be either trail or all other "simple" codecrumbs)
|
|
||||||
- select connection between two steps (code for two codecrumbs will be opened in "Sidebar" under "Crumbs" tab)
|
|
||||||
- set other options in dropdowns to configure behaviour of the diagram (show code blocks, details, etc.)
|
|
||||||
|
|
||||||
**How to get there?**
|
|
||||||
|
|
||||||
Leave breadcrumb in code by writing down a comment: ```//cc:[parameters;]```.
|
|
||||||
|
|
||||||
```cc``` (stands for "CodeCrumb") is a prefix which used by the parser; check example of parameters in the table below:
|
|
||||||
|
|
||||||
Example | Description | Use case
|
|
||||||
--- | --- | ---
|
|
||||||
```//cc:remember place``` | simple breadcrumb, ```remember place``` is a title of our first breadcrumb | Mark an important place to not forget where it was
|
|
||||||
```//cc:here is bug;well, seems like a bug in logic``` | simple breadcrumb, ```well, seems like a bug in logic``` is details for breadcrumb, separated by ```;``` | Add extra information, will be rendered in popups
|
|
||||||
```//cc:signin#3;enable route``` | trail of breadcrumbs,```signin``` is the **trail ID**, ```#3``` is order **number of step**, ```enable route``` is a title describing the step. | A sequence of codecrumbs, use to describe some data flow (e.g. user login, or form submit, etc.).
|
|
||||||
```//cc:signin#1;firebase sign in;+2;do call to firebase with credentials``` | trail of breadcrumbs,```+2``` is number of lines to highlight, separated by ```;``` | Use number of lines to highlight the code related to breadcrumb
|
|
||||||
|
|
||||||
> Note: current version supports single line comments only.
|
|
||||||
|
|
||||||
> Hint: you can use trail id without step number (e.g. ```//cc:groupname#;test```) just to group breadcrumbs, you always can add step numbers later when you know the correct order.
|
|
||||||
|
|
||||||
### Multi-codebase integration
|
|
||||||
You might be interested to study connections between several codebases (sub-modules), codecrumbs supports that.
|
|
||||||
Simply start codecrumbs multiple times (once for each codebase), it all **will be synced in one picture** inside the browser tab. To control a diagram UI - select it by clicking on it.
|
|
||||||
|
|
||||||
E.g. for client-server application, go to the source directory for your server code and run `codecrumbs -e your-server-src/index.py -d your-server-src`, same for client `codecrumbs -e src-client/index.js -d src-client`.
|
|
||||||
> **Note:** codebases can be located wherever you want (**no** need to have them like mono-repo, etc.), simply run `codecrumbs` for directory you need.
|
|
||||||
|
|
||||||
<img src="/docs/multi-codebase-cc-2.png" width="100%"/>
|
|
||||||
|
|
||||||
### Multi-language support
|
|
||||||
Current version supports next programming languages:
|
|
||||||
- `C#`
|
|
||||||
- `C++`
|
|
||||||
- `Fortran`
|
|
||||||
- `Go`
|
|
||||||
- `Haskell`
|
|
||||||
- `Java`
|
|
||||||
- `JavaScript`
|
|
||||||
- `Kotlin`
|
|
||||||
- `PHP`
|
|
||||||
- `Python`
|
|
||||||
- `Ruby`
|
|
||||||
- `TypeScript`
|
|
||||||
|
|
||||||
Please file an issue to support other language you would like to have.
|
|
||||||
|
|
||||||
### Dependencies
|
|
||||||
> Note: In current version only [JavaScript, TypeScript] offer this feature
|
|
||||||
|
|
||||||
<img src="/docs/dep-ui-2.png" width="100%"/>
|
|
||||||
|
|
||||||
UI explained:
|
|
||||||
|
|
||||||
- enable "Dependencies" switch
|
|
||||||
- select connection between modules (all involved files will be opened in "Sidebar", so you can see “what is imported” and “its implementation”)
|
|
||||||
|
|
||||||
### Flowchart
|
|
||||||
> Note: In current version only JavaScript offers this feature
|
|
||||||
|
|
||||||
<img src="/docs/flow-ui.png" width="100%"/>
|
|
||||||
|
|
||||||
[js2flowchart](https://github.com/Bogdan-Lyashenko/js-code-to-svg-flowchart) is used in the sidebar to draw flowchart for the selected file code.
|
|
||||||
|
|
||||||
## Support
|
|
||||||
Any support is very much appreciated! 👍 😘 ❤️
|
|
||||||
If you like this project, please, **put a :star: and tweet about it**. Thanks!
|
|
||||||
|
|
||||||
Please, consider [making financial donation](https://opencollective.com/codecrumbs), it will help further development of more cool features! We'll thank you by including your name/company logo here ☺️. Feel free to [ping me](https://www.linkedin.com/in/bohdan-liashenko-bb365854/) for discussion.
|
|
||||||
|
|
||||||
<a href="https://opencollective.com/codecrumbs/donate" target="_blank">
|
|
||||||
<img src="https://opencollective.com/codecrumbs/donate/button@2x.png?color=blue" width=300 />
|
|
||||||
</a>
|
|
||||||
|
|
||||||
#### Sponsors
|
|
||||||
Development supported by [0+X](https://0x.se)
|
|
||||||
|
|
||||||
<a href="https://0x.se" target="_blank">
|
|
||||||
<img src="https://avatars0.githubusercontent.com/u/16350669?s=200&v=4" width=100 />
|
|
||||||
</a>
|
|
||||||
|
|
||||||
#### Backers
|
|
||||||
<a href="https://opencollective.com/codecrumbs/backer/0/website" target="_blank"><img src="https://opencollective.com/codecrumbs/backer/0/avatar.svg"></a>
|
|
||||||
<a href="https://opencollective.com/codecrumbs/backer/1/website" target="_blank"><img src="https://opencollective.com/codecrumbs/backer/1/avatar.svg"></a>
|
|
||||||
<a href="https://opencollective.com/codecrumbs/sponsor/0/website" target="_blank"><img src="https://opencollective.com/codecrumbs/sponsor/0/avatar.svg"></a>
|
|
||||||
|
|
||||||
## Contribute
|
|
||||||
When contributing to this repository, please first discuss the change you wish to make via issue, email, or any other method with the [owner](https://github.com/Bogdan-Lyashenko/) of this repository before making a change. Ideas and suggestions are welcome.
|
|
||||||
To start development environment, clone the repo & run:
|
|
||||||
```javascript
|
|
||||||
yarn && yarn start
|
|
||||||
```
|
|
||||||
|
|
||||||
## WIP
|
|
||||||
Next features are developing:
|
|
||||||
- **VS Code extension** - some neat features right inside the code editor. Checkout [the repo here](https://github.com/Bogdan-Lyashenko/vs-code-codecrumbs).
|
|
||||||
|
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
theme: jekyll-theme-cayman
|
|
||||||
|
|
@ -1,57 +0,0 @@
|
||||||
#!/usr/bin/env node
|
|
||||||
|
|
||||||
const path = require('path');
|
|
||||||
const program = require('commander');
|
|
||||||
const colors = require('colors');
|
|
||||||
const _ = require('lodash');
|
|
||||||
|
|
||||||
const showUpdatesInfo = require('./updatesInfo');
|
|
||||||
const server = require('../src/server');
|
|
||||||
|
|
||||||
showUpdatesInfo();
|
|
||||||
|
|
||||||
program
|
|
||||||
.option('-e, --entry [entryPoint]', '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(
|
|
||||||
'-t, --tsconfig [tsConfigFile]',
|
|
||||||
'Specify path to typeScript config file. E.g. tsConfig.json'
|
|
||||||
)
|
|
||||||
.option('-p, --port [defaultPort]', 'Specify port for Codecrumbs client. E.g. 3333', 2018)
|
|
||||||
.option('-i, --ideCmd [ideCmd]', 'IDE command to open file')
|
|
||||||
.option('-x, --excludeDir [excludeDirectories]', 'Exclude directories')
|
|
||||||
.option('-n, --projectName [projectNameAlias]', 'Project name alias')
|
|
||||||
.option('-C, --configFile [pathToConfigFile]', 'Path to codecrumbs.config.js')
|
|
||||||
.option('-D, --debugModeEnabled [debugModeEnabled]', 'Enable debug mode for logs.')
|
|
||||||
.parse(process.argv);
|
|
||||||
|
|
||||||
const pathToConfigFile = program.configFile || 'codecrumbs.config.js';
|
|
||||||
const configFileExists = server.checkIfPathExists(pathToConfigFile);
|
|
||||||
if ((!program.entry || !program.dir) && !configFileExists) {
|
|
||||||
console.log(
|
|
||||||
colors.magenta(
|
|
||||||
'Please specify `entryPoint` and `projectDir` params (e.g. `codecrumbs -e src/app.js -d src`). Or use `-C codecrumbs.config.js` instead.'
|
|
||||||
)
|
|
||||||
);
|
|
||||||
process.exit();
|
|
||||||
}
|
|
||||||
|
|
||||||
const configFromFile = configFileExists ? require(path.resolve(pathToConfigFile)) : {};
|
|
||||||
|
|
||||||
const configFromCLI = {
|
|
||||||
projectNameAlias: program.projectName,
|
|
||||||
entryPoint: program.entry,
|
|
||||||
projectDir: program.dir,
|
|
||||||
webpackConfigPath: program.webpack,
|
|
||||||
tsConfigPath: program.tsconfig,
|
|
||||||
clientPort: program.port,
|
|
||||||
excludeDir: program.excludeDir,
|
|
||||||
ideCmd: program.ideCmd,
|
|
||||||
debugModeEnabled: program.debugModeEnabled
|
|
||||||
};
|
|
||||||
|
|
||||||
server.setup(_.merge(configFromCLI, configFromFile), { isDev: false });
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
const colors = require('colors');
|
|
||||||
const exec = require('child_process').exec;
|
|
||||||
|
|
||||||
module.exports = () => {
|
|
||||||
try {
|
|
||||||
exec('npm outdated codecrumbs').stdout.on('data', function(data) {
|
|
||||||
const list = data
|
|
||||||
.split(' ')
|
|
||||||
.filter(v => !!v)
|
|
||||||
.map(v => v.trim());
|
|
||||||
|
|
||||||
const latestVersion = list[list.length - 2];
|
|
||||||
console.log(
|
|
||||||
colors.cyan.underline(
|
|
||||||
`There is new version of codecrumbs (${latestVersion}) available! Please update to have all latest features and improvements!`
|
|
||||||
)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
} catch (e) {}
|
|
||||||
};
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
module.exports = {
|
|
||||||
extends: ['@commitlint/config-conventional']
|
|
||||||
};
|
|
||||||
BIN
docs/cc-ui-3.png
|
Before Width: | Height: | Size: 148 KiB |
|
Before Width: | Height: | Size: 93 KiB |
BIN
docs/dev.png
|
Before Width: | Height: | Size: 163 KiB |
BIN
docs/flow-ui.png
|
Before Width: | Height: | Size: 97 KiB |
|
Before Width: | Height: | Size: 5.1 MiB |
|
Before Width: | Height: | Size: 7.0 MiB |
BIN
docs/logo-sm.png
|
Before Width: | Height: | Size: 80 KiB |
|
Before Width: | Height: | Size: 236 KiB |
|
Before Width: | Height: | Size: 92 KiB |
|
|
@ -1,7 +0,0 @@
|
||||||
module.exports = {
|
|
||||||
entryPoint: 'example-project/src-client/index.js',
|
|
||||||
projectDir: 'example-project/src-client',
|
|
||||||
clientPort: 1234,
|
|
||||||
projectNameAlias: 'example-project-for-client',
|
|
||||||
debugModeEnabled: true
|
|
||||||
};
|
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
import move from '../zoom/move';
|
||||||
|
|
||||||
|
export default () => Promise.resolve({ data: [] });
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
//cc:debug#0;step 0
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
//cc:debug#2;step 2
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
//cc:debug#4;step 4
|
|
||||||
|
|
||||||
//cc:debug#1;step 1
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
//cc:debug#3;step 3
|
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
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();
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
#include <iostream>
|
|
||||||
using namespace std;
|
|
||||||
|
|
||||||
//cc:main function
|
|
||||||
int main()
|
|
||||||
{
|
|
||||||
cout << "Hello, World!";
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
//cc:main function
|
|
||||||
using System;
|
|
||||||
namespace HelloWorld
|
|
||||||
{
|
|
||||||
class Hello
|
|
||||||
{
|
|
||||||
static void Main()
|
|
||||||
{
|
|
||||||
Console.WriteLine("Hello World!");
|
|
||||||
|
|
||||||
// Keep the console window open in debug mode.
|
|
||||||
Console.WriteLine("Press any key to exit.");
|
|
||||||
Console.ReadKey();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
! cc: main function
|
|
||||||
program hello
|
|
||||||
print *, "Hello World!"
|
|
||||||
end program hello
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
package main
|
|
||||||
import "fmt"
|
|
||||||
|
|
||||||
// cc:main function
|
|
||||||
func main() {
|
|
||||||
fmt.Println("hello world")
|
|
||||||
}
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
-- cc:main function
|
|
||||||
putStrLn "Hello, world!"
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
public class HelloWorld {
|
|
||||||
|
|
||||||
//cc: main function
|
|
||||||
public static void main(String[] args) {
|
|
||||||
// Prints "Hello, World" to the terminal window.
|
|
||||||
System.out.println("Hello, World");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
//cc:main function
|
|
||||||
function greeting() {
|
|
||||||
console.log('Hello world!');
|
|
||||||
}
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
//cc:main function
|
|
||||||
|
|
||||||
fun main(args : Array<String>) {
|
|
||||||
println("Hello, World!")
|
|
||||||
}
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
-- hello world program
|
|
||||||
print ("Hello World!")
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
/* cc:main function */
|
|
||||||
let hello = () => "Hello, World!";
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
#!/usr/bin/perl
|
|
||||||
|
|
||||||
use strict;
|
|
||||||
use warnings;
|
|
||||||
|
|
||||||
# cc: main function.
|
|
||||||
print "Hello, World!\n";
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
<?php
|
|
||||||
//cc:main function
|
|
||||||
echo "Hello World!";
|
|
||||||
echo "PHP is so easy!";
|
|
||||||
?>
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
import sys
|
|
||||||
|
|
||||||
# cc:main function
|
|
||||||
def main(argv=None):
|
|
||||||
if argv is None:
|
|
||||||
argv = sys.argv
|
|
||||||
|
|
||||||
print "Hello, world"
|
|
||||||
|
|
||||||
return
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
# cc:main function
|
|
||||||
|
|
||||||
puts 'Hello, world!'
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
//cc:main function
|
|
||||||
function greeting() {
|
|
||||||
console.log('Hello world!');
|
|
||||||
}
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
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';
|
|
||||||
|
|
@ -1,72 +0,0 @@
|
||||||
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;+1;call to firebase with auth provider, proceed if success response
|
|
||||||
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
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
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)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
import * as authActions from './actions';
|
|
||||||
|
|
||||||
|
|
||||||
export { authActions };
|
|
||||||
export * from './action-types';
|
|
||||||
export { initAuth } from './auth';
|
|
||||||
export { authReducer } from './reducer';
|
|
||||||
export { getAuth, isAuthenticated } from './selectors';
|
|
||||||
|
|
@ -1,26 +0,0 @@
|
||||||
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#5;toggle 'authenticated' flag
|
|
||||||
id: payload ? payload.uid : null
|
|
||||||
});
|
|
||||||
|
|
||||||
case SIGN_OUT_SUCCESS:
|
|
||||||
return new AuthState();
|
|
||||||
|
|
||||||
default:
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
import { createSelector } from 'reselect';
|
|
||||||
|
|
||||||
|
|
||||||
export function isAuthenticated(state) {
|
|
||||||
return getAuth(state).authenticated;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
//=====================================
|
|
||||||
// MEMOIZED SELECTORS
|
|
||||||
//-------------------------------------
|
|
||||||
|
|
||||||
export const getAuth = createSelector(
|
|
||||||
state => state.auth,
|
|
||||||
auth => auth.toJS()
|
|
||||||
);
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
//cc:firebase config;and some details
|
|
||||||
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'
|
|
||||||
};
|
|
||||||
|
|
@ -1,86 +0,0 @@
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
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();
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
export { firebaseApp, firebaseAuth, firebaseDb } from './firebase';
|
|
||||||
export { FirebaseList } from './firebase-list';
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
import createHistory from 'history/createBrowserHistory';
|
|
||||||
|
|
||||||
|
|
||||||
export default createHistory();
|
|
||||||
|
|
@ -1,44 +0,0 @@
|
||||||
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
|
|
||||||
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 +0,0 @@
|
||||||
export const DISMISS_NOTIFICATION = 'DISMISS_NOTIFICATION';
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
import { DISMISS_NOTIFICATION } from './action-types';
|
|
||||||
|
|
||||||
|
|
||||||
export function dismissNotification() {
|
|
||||||
return {
|
|
||||||
type: DISMISS_NOTIFICATION
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
import * as notificationActions from './actions';
|
|
||||||
|
|
||||||
|
|
||||||
export { notificationActions };
|
|
||||||
export * from './action-types';
|
|
||||||
export { notificationReducer } from './reducer';
|
|
||||||
export { getNotification } from './selectors';
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
export function getNotification(state) {
|
|
||||||
return state.notification;
|
|
||||||
}
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
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
|
|
||||||
});
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
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;
|
|
||||||
};
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
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';
|
|
||||||
|
|
@ -1,124 +0,0 @@
|
||||||
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
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
import * as tasksActions from './actions';
|
|
||||||
|
|
||||||
|
|
||||||
export { tasksActions };
|
|
||||||
export * from './action-types';
|
|
||||||
export { tasksReducer } from './reducer';
|
|
||||||
export { getTaskFilter, getVisibleTasks } from './selectors';
|
|
||||||
export { Task } from './task';
|
|
||||||
|
|
@ -1,59 +0,0 @@
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,40 +0,0 @@
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
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);
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
import { Record } from 'immutable';
|
|
||||||
|
|
||||||
|
|
||||||
export const Task = new Record({
|
|
||||||
completed: false,
|
|
||||||
key: null,
|
|
||||||
title: null
|
|
||||||
});
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
|
|
||||||
<template>
|
|
||||||
<p>{{ greeting }} World!</p>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
module.exports = {
|
|
||||||
data: function () {
|
|
||||||
return {
|
|
||||||
greeting: 'Hello'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
//cc:test vue
|
|
||||||
<style scoped>
|
|
||||||
p {
|
|
||||||
font-size: 2em;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import { findRenderedComponentWithType, renderIntoDocument } from 'react-dom/test-utils';
|
|
||||||
|
|
||||||
|
|
||||||
export function createTestComponent(TestComponent, props) {
|
|
||||||
return findRenderedComponentWithType(
|
|
||||||
renderIntoDocument(<TestComponent {...props}/>),
|
|
||||||
TestComponent
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1,97 +0,0 @@
|
||||||
// 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();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,50 +0,0 @@
|
||||||
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;some details long description for separare popup
|
|
||||||
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 +0,0 @@
|
||||||
export { default } from './app';
|
|
||||||
|
|
@ -1,25 +0,0 @@
|
||||||
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;
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
@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 +0,0 @@
|
||||||
export { default } from './button';
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
export { default } from './github-logo';
|
|
||||||
|
|
@ -1,33 +0,0 @@
|
||||||
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;
|
|
||||||
|
|
@ -1,73 +0,0 @@
|
||||||
@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 +0,0 @@
|
||||||
export { default } from './header';
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
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 +0,0 @@
|
||||||
export { default } from './icon';
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
export { default } from './notification';
|
|
||||||
|
|
@ -1,58 +0,0 @@
|
||||||
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;
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
@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;
|
|
||||||
}
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
export { default } from './require-auth-route';
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import { Route, Redirect } from 'react-router-dom'
|
|
||||||
|
|
||||||
//cc:signin#6;enable route;details
|
|
||||||
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;
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
export { default } from './require-unauth-route';
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
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;
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
export { default } from './task-filters';
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
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;
|
|
||||||
|
|
@ -1,38 +0,0 @@
|
||||||
@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 +0,0 @@
|
||||||
export { default } from './task-form';
|
|
||||||
|
|
@ -1,62 +0,0 @@
|
||||||
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;
|
|
||||||
|
|
@ -1,48 +0,0 @@
|
||||||
@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 +0,0 @@
|
||||||
export { default } from './task-item';
|
|
||||||
|
|
@ -1,137 +0,0 @@
|
||||||
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;extra
|
|
||||||
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;
|
|
||||||
|
|
@ -1,124 +0,0 @@
|
||||||
@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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||