├── .gitignore
├── Gulpfile.js
├── README.md
├── gol
├── gol
│ ├── app.coffee
│ ├── dispatcher.coffee
│ ├── gol_state.coffee
│ ├── index.coffee
│ ├── initial_data.coffee
│ └── view.coffee
└── public
│ ├── index.html
│ └── js
│ └── app.js
├── hello_world
├── hello_world
│ ├── app.coffee
│ ├── dispatcher.coffee
│ ├── index.coffee
│ ├── storage.coffee
│ └── view.coffee
└── public
│ ├── index.html
│ └── js
│ └── app.js
├── hello_world2
├── hello_world2
│ ├── app.coffee
│ ├── dispatcher.coffee
│ ├── index.coffee
│ ├── storage.coffee
│ ├── transport.coffee
│ └── view
│ │ └── view.coffee
└── public
│ ├── index.html
│ └── js
│ └── app.js
├── index.html
├── package.json
└── todo
├── public
├── css
│ └── app.css
├── index.html
├── js
│ └── app.js
└── todomvc-common
│ ├── base.css
│ ├── bg.png
│ ├── bower.json
│ └── readme.md
└── todo
├── app.coffee
├── dispatcher.coffee
├── index.coffee
├── todo_state.coffee
└── views
├── todo_item.coffee
└── todo_list.coffee
/.gitignore:
--------------------------------------------------------------------------------
1 | .*
2 | lib-cov
3 | *.seed
4 | *.log
5 | *.csv
6 | *.dat
7 | *.out
8 | *.pid
9 | *.gz
10 | *.iml
11 | *.ipr
12 | *.iws
13 |
14 | pids
15 | logs
16 | results
17 | node_modules
18 | lib
19 | dist
20 | deploy.sh
21 |
22 | npm-debug.log
23 |
24 | !.gitignore
25 | !.travis.yml
26 |
--------------------------------------------------------------------------------
/Gulpfile.js:
--------------------------------------------------------------------------------
1 | var gulp = require('gulp');
2 | var browserify = require('gulp-browserify');
3 | var rename = require('gulp-rename');
4 | var uglify = require('gulp-uglify');
5 | var filter = require('gulp-filter');
6 | var deploy = require('gulp-gh-pages');
7 | var react_render = require('gulp-react-render');
8 | require('coffee-script/register');
9 |
10 |
11 | var apps = ['hello_world', 'todo', 'hello_world2', "nested_components", "gol"];
12 |
13 |
14 | // Build
15 | apps.map(function(app){
16 | gulp.task(app, function(){
17 | return gulp.src('./' + app + '/' + app + '/index.coffee', {read: false})
18 | .pipe(browserify({
19 | transform: ['coffeeify'],
20 | extensions: ['.coffee']}))
21 | .pipe(rename('app.js'))
22 | .pipe(gulp.dest('./' + app + '/public/js'));
23 | });
24 | });
25 |
26 | // Deploy
27 | apps.map(function(app){
28 | gulp.task('deploy_' + app, function(){
29 | jsfilter = filter(['**/*.js'])
30 | htmlfilter = filter(['**/*.html'])
31 |
32 | return gulp.src('./' + app + '/public/**/*.*')
33 | .pipe(jsfilter)
34 | .pipe(uglify())
35 | .pipe(jsfilter.restore())
36 | .pipe(htmlfilter)
37 | .pipe(react_render())
38 | .pipe(htmlfilter.restore())
39 | .pipe(gulp.dest('./dist/' + app + '/public'));
40 | });
41 | });
42 |
43 | // Watch
44 | gulp.task('watch', function(){
45 | apps.map(function(app){
46 | gulp.watch('./' + app + '/' + app + '/**/*.*', [app]);
47 | });
48 | });
49 |
50 | gulp.task('default', apps);
51 |
52 | gulp.task('copy_main_index', function(){
53 | return gulp.src("index.html")
54 | .pipe(react_render())
55 | .pipe(gulp.dest('./dist'));
56 | });
57 |
58 | gulp.task('deploy_apps', ['default'], function() {
59 | return apps.map(function(app){return 'deploy_' + app});
60 | });
61 |
62 |
63 | gulp.task('deploy', ['deploy_apps', 'copy_main_index'], function (){
64 | return gulp.src("./dist/**/*")
65 | .pipe(deploy());
66 | });
67 |
68 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # RxReact
2 |
3 | Demo apps:
4 | [Site with demo apps](http://alexmost.github.io/RxReact/)
5 |
--------------------------------------------------------------------------------
/gol/gol/app.coffee:
--------------------------------------------------------------------------------
1 | Rx = require 'rx'
2 | React = require 'react'
3 | MainView = React.createFactory(require './view')
4 | {GolState, addCols, addRows, STATUS} = require './gol_state'
5 | dispatchActions = require './dispatcher'
6 | Immutable = require 'immutable'
7 | List = Immutable.List
8 |
9 | initialData = require './initial_data'
10 |
11 | getViewState = (state) ->
12 | cells: state.get("cells")
13 | isPlay: state.get("status") == STATUS.PLAY
14 |
15 |
16 | initApp = (mountNode) ->
17 | eventStream = new Rx.Subject()
18 |
19 | initialState = GolState(cells: Immutable.fromJS(initialData))
20 |
21 | view = React.render MainView({eventStream}), mountNode
22 |
23 | stateStream = dispatchActions(initialState, eventStream)
24 |
25 | stateStream.subscribe(
26 | (newState) -> view.setProps getViewState(newState)
27 | (err) -> throw new Error err.stack)
28 |
29 | eventStream.onNext {action: "play"}
30 |
31 |
32 | module.exports = initApp
--------------------------------------------------------------------------------
/gol/gol/dispatcher.coffee:
--------------------------------------------------------------------------------
1 | Rx = require 'rx'
2 | {calcNewState, addPoint, addRow, addCol, play, stop, delPoint, saveSate} = require './gol_state'
3 |
4 |
5 | dispatchActions = (initialState, eventStream) ->
6 | pauser = new Rx.Subject
7 |
8 | playStream = eventStream.filter(({action}) -> action is "play")
9 | .map(-> play)
10 | .do(-> pauser.onNext(true))
11 |
12 | stopStream = eventStream.filter(({action}) -> action is "stop")
13 | .map(-> stop)
14 | .do(-> pauser.onNext(false))
15 |
16 | gameLoopStream = Rx.Observable.interval(50).pausable(pauser).map(-> calcNewState)
17 |
18 | addRowStream = eventStream.filter(({action}) -> action is "add_row")
19 | .map(-> addRow)
20 |
21 | addColStream = eventStream.filter(({action}) -> action is "add_col")
22 | .map(-> addCol)
23 |
24 | mouseDown = eventStream.filter(({action}) -> action is "on_cell_mouse_down")
25 | mouseMove = eventStream.filter(({action}) -> action is "on_hover")
26 | mouseUp = eventStream.filter(({action}) -> action is "on_cell_mouse_up")
27 |
28 | addPointStream = eventStream.filter(({action}) -> action is "add_point")
29 | .map(({point}) -> addPoint(point))
30 |
31 | delPointStream = eventStream.filter(({action}) -> action is "del_point")
32 | .map(({point}) -> delPoint(point))
33 |
34 | saveState = eventStream.filter(({action}) -> action is "save")
35 | .map(-> saveSate)
36 |
37 | Rx.Observable.merge(
38 | gameLoopStream, addPointStream, addRowStream,
39 | addColStream, playStream, stopStream, delPointStream,
40 | saveState)
41 | .scan(initialState, (state, action) -> action(state))
42 | .startWith(initialState)
43 |
44 |
45 | module.exports = dispatchActions
--------------------------------------------------------------------------------
/gol/gol/gol_state.coffee:
--------------------------------------------------------------------------------
1 | Immutable = require 'immutable'
2 | Record = Immutable.Record
3 | List = Immutable.List
4 |
5 | STATUS = {PLAY: 0, STOP: 1}
6 |
7 | GolState = Record
8 | cells: List(List())
9 | status: STATUS.PLAY
10 | saves: List()
11 |
12 |
13 | LIVE = 1
14 | DEAD = 0
15 |
16 |
17 | findNeighbors = ([y, x], matrix) ->
18 | [[y-1, x-1], [y, x-1], [y+1, x-1], [y-1, x],
19 | [y+1, x], [y-1, x+1], [y, x+1], [y+1, x+1]]
20 | .filter(([y, x]) -> y >= 0 and x >= 0) # immutable js returns last element instead of undefined if negative index
21 | .map(([y, x]) -> matrix.get(y)?.get(x))
22 | .filter((value) -> value?)
23 |
24 |
25 | calcNewState = (state) ->
26 | currentCells = state.get("cells")
27 | currentCells.forEach((row, rowNum) ->
28 | row.forEach((cell, cellNum) ->
29 | neighbors = findNeighbors([rowNum, cellNum], currentCells)
30 | live_count = neighbors.filter((n) -> n is LIVE).length
31 | dead_count = neighbors.length - live_count
32 |
33 | cell_value = if cell is LIVE
34 | if live_count < 2 or live_count > 3
35 | DEAD
36 | else
37 | LIVE
38 | else # dead
39 | if live_count is 3
40 | LIVE
41 | else
42 | DEAD
43 | state = state.setIn(["cells", rowNum, cellNum], cell_value)
44 | )
45 | )
46 | state
47 |
48 |
49 | addPoint = ([y, x]) -> (state) -> state.setIn(["cells", y, x], LIVE)
50 |
51 |
52 | delPoint = ([y, x]) -> (state) -> state.setIn(["cells", y, x], DEAD)
53 |
54 |
55 | addRow = (state) ->
56 | currentCells = state.get("cells")
57 | rowLength = currentCells.get(0).size
58 | new_row = List((DEAD for i in [0..rowLength]))
59 | state.set("cells", currentCells.push(new_row))
60 |
61 |
62 | addCol = (state) ->
63 | currentCells = state.get("cells")
64 | newCells = currentCells.map((row) ->
65 | row.push(DEAD))
66 | state.set("cells", newCells)
67 |
68 |
69 | addCols = (n) -> (state) ->
70 | [0..n].reduce(((s, i) -> addCol(s)), state)
71 |
72 |
73 | addRows = (n) -> (state) ->
74 | [0..n].reduce(((s, i) -> addRow(s)), state)
75 |
76 |
77 | stop = (state) -> state.set("status", STATUS.STOP)
78 |
79 |
80 | play = (state) -> state.set("status", STATUS.PLAY)
81 |
82 |
83 | saveSate = (state) ->
84 | console.log JSON.stringify(state.get("cells").toJS())
85 | state.set("saves", state.get("saves").push(state.get("cells")))
86 |
87 |
88 | module.exports = {
89 | GolState, calcNewState, addPoint,
90 | addRow, addCol, addCols, addRows, STATUS,
91 | stop, play, delPoint, saveSate
92 | }
--------------------------------------------------------------------------------
/gol/gol/index.coffee:
--------------------------------------------------------------------------------
1 | initApp = require './app'
2 |
3 | # Mounting application
4 | initApp(document.getElementById('hello_container'))
--------------------------------------------------------------------------------
/gol/gol/initial_data.coffee:
--------------------------------------------------------------------------------
1 | module.exports = [[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,1,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,1,1,0,0,0,0,0,0,0,0,1,0,0,0,1,0,1,1,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]]
--------------------------------------------------------------------------------
/gol/gol/view.coffee:
--------------------------------------------------------------------------------
1 | React = require 'react'
2 | createFactory = React.createFactory
3 | {div, button, table, tbody, tr, td} = React.DOM
4 | PureRenderMixin = require('react/addons').addons.PureRenderMixin
5 | Immutable = require 'immutable'
6 | List = Immutable.List
7 |
8 |
9 | BlackCellClass = React.createClass
10 | render: ->
11 | td
12 | className: "live"
13 | onClick: @props.onSelect
14 |
15 |
16 | WhiteCellClass = React.createClass
17 | render: ->
18 | td
19 | className: "dead"
20 | onClick: @props.onSelect
21 |
22 |
23 |
24 | BlackCell = createFactory(BlackCellClass)
25 | WhiteCell = createFactory(WhiteCellClass)
26 |
27 |
28 | MainView = React.createClass
29 | mixins: [PureRenderMixin]
30 |
31 | getDefaultProps: ->
32 | cells: List(List())
33 |
34 | render: ->
35 | div null,
36 | if @props.isPlay
37 | button
38 | onClick: => @props.eventStream.onNext
39 | action: "stop"
40 | "stop"
41 | else
42 | button
43 | onClick: => @props.eventStream.onNext
44 | action: "play"
45 |
46 | "play"
47 |
48 | button
49 | onClick: => @props.eventStream.onNext {action: "save"}
50 | "dump"
51 |
52 | table
53 | style: {border: "1px solid gray"}
54 | cellPadding: 0
55 | cellSpacing: 0
56 | tbody null,
57 | @props.cells.toArray().map((row, i) =>
58 | tr {key: i},
59 | row.toArray().map((cell, j) =>
60 | if cell
61 | BlackCell
62 | key: j
63 | onSelect: => @props.eventStream.onNext {
64 | action: "del_point"
65 | point: [i, j]
66 | }
67 | else
68 | WhiteCell
69 | key: j
70 | onSelect: => @props.eventStream.onNext {
71 | action: "add_point"
72 | point: [i, j]
73 | }
74 | )
75 | )
76 |
77 |
78 | module.exports = MainView
79 |
80 |
--------------------------------------------------------------------------------
/gol/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Rx.js + React.js • Gol
6 |
7 |
29 |
30 | Gol in RxReact
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/hello_world/hello_world/app.coffee:
--------------------------------------------------------------------------------
1 | Rx = require 'rx'
2 | React = require 'react'
3 | HelloView = React.createFactory(require './view')
4 | HelloStorage = require './storage'
5 | dispatchActions = require './dispatcher'
6 |
7 |
8 | initApp = (mountNode) ->
9 | eventStream = new Rx.Subject()
10 | store = new HelloStorage()
11 | view = React.render HelloView({eventStream}), mountNode
12 | dispatchActions(view, eventStream, store)
13 |
14 |
15 | module.exports = initApp
--------------------------------------------------------------------------------
/hello_world/hello_world/dispatcher.coffee:
--------------------------------------------------------------------------------
1 | Rx = require 'rx'
2 |
3 |
4 | getViewState = (store) ->
5 | clicksCount: store.getClicksCount()
6 |
7 |
8 | dispatchActions = (view, eventStream, store) ->
9 | incrementClickStream = eventStream
10 | .filter(({action}) -> action is "increment_click_count")
11 | .do(-> store.incrementClicksCount())
12 |
13 | Rx.Observable.merge(
14 | incrementClickStream
15 | # some more actions here for updating view ...
16 |
17 | ).subscribe(
18 | -> view.setProps getViewState(store)
19 | (err) ->
20 | console.error? err)
21 |
22 |
23 | module.exports = dispatchActions
--------------------------------------------------------------------------------
/hello_world/hello_world/index.coffee:
--------------------------------------------------------------------------------
1 | initApp = require './app'
2 |
3 | # Mounting application
4 | initApp(document.getElementById('hello_container'))
--------------------------------------------------------------------------------
/hello_world/hello_world/storage.coffee:
--------------------------------------------------------------------------------
1 | class HelloStorage
2 | constructor: ->
3 | @clicksCount = 0
4 |
5 | getClicksCount: -> @clicksCount
6 |
7 | incrementClicksCount: ->
8 | @clicksCount += 1
9 |
10 | module.exports = HelloStorage
--------------------------------------------------------------------------------
/hello_world/hello_world/view.coffee:
--------------------------------------------------------------------------------
1 | React = require 'react'
2 | {div, button} = React.DOM
3 |
4 |
5 | HelloView = React.createClass
6 | getDefaultProps: ->
7 | clicksCount: 0
8 |
9 | incrementClickCount: ->
10 | @props.eventStream.onNext
11 | action: "increment_click_count"
12 |
13 | render: ->
14 | div null,
15 | div null, "You clicked #{@props.clicksCount} times"
16 | button
17 | onClick: @incrementClickCount
18 | "Click"
19 |
20 |
21 | module.exports = HelloView
22 |
23 |
--------------------------------------------------------------------------------
/hello_world/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Rx.js + React.js • HelloWorld
6 |
7 |
8 | Hello World for RxReact
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/hello_world2/hello_world2/app.coffee:
--------------------------------------------------------------------------------
1 | Rx = require 'rx'
2 | React = require 'react'
3 | HelloView = React.createFactory(require './view/view')
4 | HelloStorage = require './storage'
5 | dispatchActions = require './dispatcher'
6 |
7 |
8 | initApp = (mountNode) ->
9 | eventStream = new Rx.Subject()
10 | store = new HelloStorage()
11 | view = React.render HelloView({eventStream}), mountNode
12 | dispatchActions(view, eventStream, store)
13 |
14 |
15 | module.exports = initApp
--------------------------------------------------------------------------------
/hello_world2/hello_world2/dispatcher.coffee:
--------------------------------------------------------------------------------
1 | Rx = require 'rx'
2 | {saveToDb} = require './transport'
3 |
4 | getViewState = (store) ->
5 | clicksCount: store.getClicksCount()
6 | showSavedMessage: store.getShowSavedMessage()
7 |
8 |
9 | dispatchActions = (view, eventStream, store) ->
10 | incrementClickStream = eventStream
11 | .filter(({action}) -> action is "increment_click_count")
12 | .do(-> store.incrementClicksCount())
13 | .share()
14 |
15 | decrementClickStream = eventStream
16 | .filter(({action}) -> action is "decrement_click_count")
17 | .do(-> store.decrementClickscount())
18 | .share()
19 |
20 | countClicksStream = Rx.Observable
21 | .merge(incrementClickStream, decrementClickStream)
22 |
23 | showSavedMessageStream = countClicksStream
24 | .throttle(1000)
25 | .distinct(-> store.getClicksCount())
26 | .flatMap(-> saveToDb store.getClicksCount())
27 | .do(-> store.enableSavedMessage())
28 |
29 | hideSavedMessageStream = showSavedMessageStream.delay(2000)
30 | .do(-> store.disableSavedMessage())
31 |
32 | Rx.Observable.merge(
33 | countClicksStream
34 | showSavedMessageStream
35 | hideSavedMessageStream
36 | # some more actions here for updating view ...
37 |
38 | ).subscribe(
39 | -> view.setProps getViewState(store)
40 | (err) ->
41 | console.error? err)
42 |
43 |
44 | module.exports = dispatchActions
--------------------------------------------------------------------------------
/hello_world2/hello_world2/index.coffee:
--------------------------------------------------------------------------------
1 | initApp = require './app'
2 |
3 | # Mounting application
4 | initApp(document.getElementById('hello_container'))
--------------------------------------------------------------------------------
/hello_world2/hello_world2/storage.coffee:
--------------------------------------------------------------------------------
1 | class HelloStorage
2 | constructor: ->
3 | @clicksCount = 0
4 | @showSavedMessage = false
5 |
6 | getClicksCount: -> @clicksCount
7 |
8 | incrementClicksCount: ->
9 | @clicksCount += 1
10 |
11 | decrementClickscount: ->
12 | @clicksCount -= 1
13 |
14 | getShowSavedMessage: -> @showSavedMessage
15 |
16 | enableSavedMessage: ->
17 | @showSavedMessage = true
18 |
19 | disableSavedMessage: ->
20 | @showSavedMessage = false
21 |
22 |
23 | module.exports = HelloStorage
--------------------------------------------------------------------------------
/hello_world2/hello_world2/transport.coffee:
--------------------------------------------------------------------------------
1 | Rx = require 'rx'
2 |
3 | # Mock for some async operation
4 | saveToDb = (value) ->
5 | Rx.Observable.create (observer) ->
6 | setTimeout(
7 | ->
8 | observer.onNext value
9 | observer.onCompleted()
10 | 2000
11 | )
12 |
13 | module.exports = {saveToDb}
14 |
--------------------------------------------------------------------------------
/hello_world2/hello_world2/view/view.coffee:
--------------------------------------------------------------------------------
1 | React = require 'react'
2 | {div, button, h1, p} = React.DOM
3 |
4 |
5 | HelloView = React.createClass
6 | getDefaultProps: ->
7 | clicksCount: 0
8 |
9 | incrementClickCount: ->
10 | @props.eventStream.onNext
11 | action: "increment_click_count"
12 |
13 | decrementClickCount: ->
14 | @props.eventStream.onNext
15 | action: "decrement_click_count"
16 |
17 | render: ->
18 | div null,
19 | div null, "Hello"
20 |
21 | if @props.showSavedMessage
22 | p {style: {color: "red"}}, "Count saved"
23 |
24 | div null, "You clicked #{@props.clicksCount} times"
25 | button
26 | onClick: @incrementClickCount
27 | "Click +1"
28 |
29 | button
30 | onClick: @decrementClickCount
31 | "Click -1"
32 |
33 |
34 | module.exports = HelloView
35 |
36 |
--------------------------------------------------------------------------------
/hello_world2/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Rx.js + React.js • HelloWorld
6 |
7 |
8 | Hello World for RxReact
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | RxReact = Rx.js + React.js
6 |
7 |
8 |
9 |
10 | RxReact (Rx.js + React.js)
11 | Yet another way to build your kickass javascript application
12 | More info will be soon...
13 |
14 | Demo
15 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "todo",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "author": "",
10 | "license": "ISC",
11 | "dependencies": {
12 | "immutable": "^3.7.3",
13 | "react": "^0.13.1",
14 | "rx": "^2.3.24"
15 | },
16 | "devDependencies": {
17 | "coffeeify": "^1.0.0",
18 | "gulp": "^3.8.10",
19 | "gulp-browserify": "^0.5.1",
20 | "gulp-filter": "^2.0.0",
21 | "gulp-gh-pages": "^0.4.0",
22 | "gulp-react-render": "^1.0.0",
23 | "gulp-rename": "^1.2.0",
24 | "gulp-uglify": "^1.1.0"
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/todo/public/css/app.css:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2014, Facebook, Inc.
3 | * All rights reserved.
4 | *
5 | * This source code is licensed under the BSD-style license found in the
6 | * LICENSE file in the root directory of this source tree. An additional grant
7 | * of patent rights can be found in the PATENTS file in the same directory.
8 | *
9 | * base.css overrides
10 | */
11 |
12 | /**
13 | * We are not changing from display:none, but rather re-rendering instead.
14 | * Therefore this needs to be displayed normally by default.
15 | */
16 | #todo-list li .edit {
17 | display: inline;
18 | }
19 |
--------------------------------------------------------------------------------
/todo/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Rx.js + React.js • TodoMVC
6 |
7 |
8 |
9 |
10 |
11 |
12 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/todo/public/todomvc-common/base.css:
--------------------------------------------------------------------------------
1 | html,
2 | body {
3 | margin: 0;
4 | padding: 0;
5 | }
6 |
7 | button {
8 | margin: 0;
9 | padding: 0;
10 | border: 0;
11 | background: none;
12 | font-size: 100%;
13 | vertical-align: baseline;
14 | font-family: inherit;
15 | color: inherit;
16 | -webkit-appearance: none;
17 | -ms-appearance: none;
18 | -o-appearance: none;
19 | appearance: none;
20 | }
21 |
22 | body {
23 | font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif;
24 | line-height: 1.4em;
25 | background: #eaeaea url('bg.png');
26 | color: #4d4d4d;
27 | width: 550px;
28 | margin: 0 auto;
29 | -webkit-font-smoothing: antialiased;
30 | -moz-font-smoothing: antialiased;
31 | -ms-font-smoothing: antialiased;
32 | -o-font-smoothing: antialiased;
33 | font-smoothing: antialiased;
34 | }
35 |
36 | button,
37 | input[type="checkbox"] {
38 | outline: none;
39 | }
40 |
41 | #todoapp {
42 | background: #fff;
43 | background: rgba(255, 255, 255, 0.9);
44 | margin: 130px 0 40px 0;
45 | border: 1px solid #ccc;
46 | position: relative;
47 | border-top-left-radius: 2px;
48 | border-top-right-radius: 2px;
49 | box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.2),
50 | 0 25px 50px 0 rgba(0, 0, 0, 0.15);
51 | }
52 |
53 | #todoapp:before {
54 | content: '';
55 | border-left: 1px solid #f5d6d6;
56 | border-right: 1px solid #f5d6d6;
57 | width: 2px;
58 | position: absolute;
59 | top: 0;
60 | left: 40px;
61 | height: 100%;
62 | }
63 |
64 | #todoapp input::-webkit-input-placeholder {
65 | font-style: italic;
66 | }
67 |
68 | #todoapp input::-moz-placeholder {
69 | font-style: italic;
70 | color: #a9a9a9;
71 | }
72 |
73 | #todoapp h1 {
74 | position: absolute;
75 | top: -120px;
76 | width: 100%;
77 | font-size: 70px;
78 | font-weight: bold;
79 | text-align: center;
80 | color: #b3b3b3;
81 | color: rgba(255, 255, 255, 0.3);
82 | text-shadow: -1px -1px rgba(0, 0, 0, 0.2);
83 | -webkit-text-rendering: optimizeLegibility;
84 | -moz-text-rendering: optimizeLegibility;
85 | -ms-text-rendering: optimizeLegibility;
86 | -o-text-rendering: optimizeLegibility;
87 | text-rendering: optimizeLegibility;
88 | }
89 |
90 | #addbtn {
91 | position: absolute;
92 | top: -130px;
93 | background: rgba(122, 65, 10, 0.71);
94 | color: white;
95 | font-weight: bold;
96 | outline: auto;
97 | cursor: pointer;
98 | padding: 10px;
99 | width: 100%;
100 | }
101 |
102 | #header {
103 | padding-top: 15px;
104 | border-radius: inherit;
105 | }
106 |
107 | #header:before {
108 | content: '';
109 | position: absolute;
110 | top: 0;
111 | right: 0;
112 | left: 0;
113 | height: 15px;
114 | z-index: 2;
115 | border-bottom: 1px solid #6c615c;
116 | background: #8d7d77;
117 | background: -webkit-gradient(linear, left top, left bottom, from(rgba(132, 110, 100, 0.8)),to(rgba(101, 84, 76, 0.8)));
118 | background: -webkit-linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8));
119 | background: linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8));
120 | filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0,StartColorStr='#9d8b83', EndColorStr='#847670');
121 | border-top-left-radius: 1px;
122 | border-top-right-radius: 1px;
123 | }
124 |
125 | #new-todo,
126 | .edit {
127 | position: relative;
128 | margin: 0;
129 | width: 100%;
130 | font-size: 24px;
131 | font-family: inherit;
132 | line-height: 1.4em;
133 | border: 0;
134 | outline: none;
135 | color: inherit;
136 | padding: 6px;
137 | border: 1px solid #999;
138 | box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2);
139 | -moz-box-sizing: border-box;
140 | -ms-box-sizing: border-box;
141 | -o-box-sizing: border-box;
142 | box-sizing: border-box;
143 | -webkit-font-smoothing: antialiased;
144 | -moz-font-smoothing: antialiased;
145 | -ms-font-smoothing: antialiased;
146 | -o-font-smoothing: antialiased;
147 | font-smoothing: antialiased;
148 | }
149 |
150 | #new-todo {
151 | padding: 16px 16px 16px 60px;
152 | border: none;
153 | background: rgba(0, 0, 0, 0.02);
154 | z-index: 2;
155 | box-shadow: none;
156 | }
157 |
158 | #main {
159 | position: relative;
160 | z-index: 2;
161 | border-top: 1px dotted #adadad;
162 | }
163 |
164 | label[for='toggle-all'] {
165 | display: none;
166 | }
167 |
168 | #toggle-all {
169 | position: absolute;
170 | top: -42px;
171 | left: -4px;
172 | width: 40px;
173 | text-align: center;
174 | /* Mobile Safari */
175 | border: none;
176 | }
177 |
178 | #toggle-all:before {
179 | content: '»';
180 | font-size: 28px;
181 | color: #d9d9d9;
182 | padding: 0 25px 7px;
183 | }
184 |
185 | #toggle-all:checked:before {
186 | color: #737373;
187 | }
188 |
189 | #todo-list {
190 | margin: 0;
191 | padding: 0;
192 | list-style: none;
193 | }
194 |
195 | #todo-list li {
196 | position: relative;
197 | font-size: 24px;
198 | border-bottom: 1px dotted #ccc;
199 | }
200 |
201 | #todo-list li:last-child {
202 | border-bottom: none;
203 | }
204 |
205 | #todo-list li.editing {
206 | border-bottom: none;
207 | padding: 0;
208 | }
209 |
210 | #todo-list li.editing .edit {
211 | display: block;
212 | width: 506px;
213 | padding: 13px 17px 12px 17px;
214 | margin: 0 0 0 43px;
215 | }
216 |
217 | #todo-list li.editing .view {
218 | display: none;
219 | }
220 |
221 | #todo-list li .toggle {
222 | text-align: center;
223 | width: 40px;
224 | /* auto, since non-WebKit browsers doesn't support input styling */
225 | height: auto;
226 | position: absolute;
227 | top: 0;
228 | bottom: 0;
229 | margin: auto 0;
230 | /* Mobile Safari */
231 | border: none;
232 | -webkit-appearance: none;
233 | -ms-appearance: none;
234 | -o-appearance: none;
235 | appearance: none;
236 | }
237 |
238 | #todo-list li .toggle:after {
239 | content: '✔';
240 | /* 40 + a couple of pixels visual adjustment */
241 | line-height: 43px;
242 | font-size: 20px;
243 | color: #d9d9d9;
244 | text-shadow: 0 -1px 0 #bfbfbf;
245 | }
246 |
247 | #todo-list li .toggle:checked:after {
248 | color: #85ada7;
249 | text-shadow: 0 1px 0 #669991;
250 | bottom: 1px;
251 | position: relative;
252 | }
253 |
254 | #todo-list li label {
255 | white-space: pre;
256 | word-break: break-word;
257 | padding: 15px 60px 15px 15px;
258 | margin-left: 45px;
259 | display: block;
260 | line-height: 1.2;
261 | -webkit-transition: color 0.4s;
262 | transition: color 0.4s;
263 | }
264 |
265 | #todo-list li.completed label {
266 | color: #a9a9a9;
267 | text-decoration: line-through;
268 | }
269 |
270 | #todo-list li .destroy {
271 | display: none;
272 | position: absolute;
273 | top: 0;
274 | right: 10px;
275 | bottom: 0;
276 | width: 40px;
277 | height: 40px;
278 | margin: auto 0;
279 | font-size: 22px;
280 | color: #a88a8a;
281 | -webkit-transition: all 0.2s;
282 | transition: all 0.2s;
283 | }
284 |
285 | #todo-list li .destroy:hover {
286 | text-shadow: 0 0 1px #000,
287 | 0 0 10px rgba(199, 107, 107, 0.8);
288 | -webkit-transform: scale(1.3);
289 | -ms-transform: scale(1.3);
290 | transform: scale(1.3);
291 | }
292 |
293 | #todo-list li .destroy:after {
294 | content: '✖';
295 | }
296 |
297 | #todo-list li:hover .destroy {
298 | display: block;
299 | }
300 |
301 | #todo-list li .edit {
302 | display: none;
303 | }
304 |
305 | #todo-list li.editing:last-child {
306 | margin-bottom: -1px;
307 | }
308 |
309 | #footer {
310 | color: #777;
311 | padding: 0 15px;
312 | position: absolute;
313 | right: 0;
314 | bottom: -31px;
315 | left: 0;
316 | height: 20px;
317 | z-index: 1;
318 | text-align: center;
319 | }
320 |
321 | #footer:before {
322 | content: '';
323 | position: absolute;
324 | right: 0;
325 | bottom: 31px;
326 | left: 0;
327 | height: 50px;
328 | z-index: -1;
329 | box-shadow: 0 1px 1px rgba(0, 0, 0, 0.3),
330 | 0 6px 0 -3px rgba(255, 255, 255, 0.8),
331 | 0 7px 1px -3px rgba(0, 0, 0, 0.3),
332 | 0 43px 0 -6px rgba(255, 255, 255, 0.8),
333 | 0 44px 2px -6px rgba(0, 0, 0, 0.2);
334 | }
335 |
336 | #todo-count {
337 | float: left;
338 | text-align: left;
339 | }
340 |
341 | #filters {
342 | margin: 0;
343 | padding: 0;
344 | list-style: none;
345 | position: absolute;
346 | right: 0;
347 | left: 0;
348 | }
349 |
350 | #filters li {
351 | display: inline;
352 | }
353 |
354 | #filters li a {
355 | color: #83756f;
356 | margin: 2px;
357 | text-decoration: none;
358 | }
359 |
360 | #filters li a.selected {
361 | font-weight: bold;
362 | }
363 |
364 | #clear-completed {
365 | float: right;
366 | position: relative;
367 | line-height: 20px;
368 | text-decoration: none;
369 | background: rgba(0, 0, 0, 0.1);
370 | font-size: 11px;
371 | padding: 0 10px;
372 | border-radius: 3px;
373 | box-shadow: 0 -1px 0 0 rgba(0, 0, 0, 0.2);
374 | }
375 |
376 | #clear-completed:hover {
377 | background: rgba(0, 0, 0, 0.15);
378 | box-shadow: 0 -1px 0 0 rgba(0, 0, 0, 0.3);
379 | }
380 |
381 | #info {
382 | margin: 65px auto 0;
383 | color: #a6a6a6;
384 | font-size: 12px;
385 | text-shadow: 0 1px 0 rgba(255, 255, 255, 0.7);
386 | text-align: center;
387 | }
388 |
389 | #info a {
390 | color: inherit;
391 | }
392 |
393 | /*
394 | Hack to remove background from Mobile Safari.
395 | Can't use it globally since it destroys checkboxes in Firefox and Opera
396 | */
397 |
398 | @media screen and (-webkit-min-device-pixel-ratio:0) {
399 | #toggle-all,
400 | #todo-list li .toggle {
401 | background: none;
402 | }
403 |
404 | #todo-list li .toggle {
405 | height: 40px;
406 | }
407 |
408 | #toggle-all {
409 | top: -56px;
410 | left: -15px;
411 | width: 65px;
412 | height: 41px;
413 | -webkit-transform: rotate(90deg);
414 | -ms-transform: rotate(90deg);
415 | transform: rotate(90deg);
416 | -webkit-appearance: none;
417 | appearance: none;
418 | }
419 | }
420 |
421 | .hidden {
422 | display: none;
423 | }
424 |
425 | hr {
426 | margin: 20px 0;
427 | border: 0;
428 | border-top: 1px dashed #C5C5C5;
429 | border-bottom: 1px dashed #F7F7F7;
430 | }
431 |
432 | .learn a {
433 | font-weight: normal;
434 | text-decoration: none;
435 | color: #b83f45;
436 | }
437 |
438 | .learn a:hover {
439 | text-decoration: underline;
440 | color: #787e7e;
441 | }
442 |
443 | .learn h3,
444 | .learn h4,
445 | .learn h5 {
446 | margin: 10px 0;
447 | font-weight: 500;
448 | line-height: 1.2;
449 | color: #000;
450 | }
451 |
452 | .learn h3 {
453 | font-size: 24px;
454 | }
455 |
456 | .learn h4 {
457 | font-size: 18px;
458 | }
459 |
460 | .learn h5 {
461 | margin-bottom: 0;
462 | font-size: 14px;
463 | }
464 |
465 | .learn ul {
466 | padding: 0;
467 | margin: 0 0 30px 25px;
468 | }
469 |
470 | .learn li {
471 | line-height: 20px;
472 | }
473 |
474 | .learn p {
475 | font-size: 15px;
476 | font-weight: 300;
477 | line-height: 1.3;
478 | margin-top: 0;
479 | margin-bottom: 0;
480 | }
481 |
482 | .quote {
483 | border: none;
484 | margin: 20px 0 60px 0;
485 | }
486 |
487 | .quote p {
488 | font-style: italic;
489 | }
490 |
491 | .quote p:before {
492 | content: '“';
493 | font-size: 50px;
494 | opacity: .15;
495 | position: absolute;
496 | top: -20px;
497 | left: 3px;
498 | }
499 |
500 | .quote p:after {
501 | content: '”';
502 | font-size: 50px;
503 | opacity: .15;
504 | position: absolute;
505 | bottom: -42px;
506 | right: 3px;
507 | }
508 |
509 | .quote footer {
510 | position: absolute;
511 | bottom: -40px;
512 | right: 0;
513 | }
514 |
515 | .quote footer img {
516 | border-radius: 3px;
517 | }
518 |
519 | .quote footer a {
520 | margin-left: 5px;
521 | vertical-align: middle;
522 | }
523 |
524 | .speech-bubble {
525 | position: relative;
526 | padding: 10px;
527 | background: rgba(0, 0, 0, .04);
528 | border-radius: 5px;
529 | }
530 |
531 | .speech-bubble:after {
532 | content: '';
533 | position: absolute;
534 | top: 100%;
535 | right: 30px;
536 | border: 13px solid transparent;
537 | border-top-color: rgba(0, 0, 0, .04);
538 | }
539 |
540 | .learn-bar > .learn {
541 | position: absolute;
542 | width: 272px;
543 | top: 8px;
544 | left: -300px;
545 | padding: 10px;
546 | border-radius: 5px;
547 | background-color: rgba(255, 255, 255, .6);
548 | -webkit-transition-property: left;
549 | transition-property: left;
550 | -webkit-transition-duration: 500ms;
551 | transition-duration: 500ms;
552 | }
553 |
554 | @media (min-width: 899px) {
555 | .learn-bar {
556 | width: auto;
557 | margin: 0 0 0 300px;
558 | }
559 |
560 | .learn-bar > .learn {
561 | left: 8px;
562 | }
563 |
564 | .learn-bar #todoapp {
565 | width: 550px;
566 | margin: 130px auto 40px auto;
567 | }
568 | }
569 |
--------------------------------------------------------------------------------
/todo/public/todomvc-common/bg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AlexMost/RxReact/12e28e75f2861c7c29a5222238126d3c864ab9a2/todo/public/todomvc-common/bg.png
--------------------------------------------------------------------------------
/todo/public/todomvc-common/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "todomvc-common",
3 | "version": "0.1.9"
4 | }
5 |
--------------------------------------------------------------------------------
/todo/public/todomvc-common/readme.md:
--------------------------------------------------------------------------------
1 | # todomvc-common
2 |
3 | > Bower component for some common utilities we use in every app
4 |
5 |
6 | ## License
7 |
8 | MIT
9 |
--------------------------------------------------------------------------------
/todo/todo/app.coffee:
--------------------------------------------------------------------------------
1 | Rx = require 'rx'
2 | React = require 'react'
3 | {createFactory} = React
4 | TodoComponent = createFactory(require './views/todo_list')
5 | {TodoListState} = require './todo_state'
6 | dispatchActions = require './dispatcher'
7 |
8 |
9 | getViewState = (state) ->
10 | todoText: state.get("todoText")
11 | todoItems: state.get("todoItems")
12 |
13 | initApp = (mountNode, state, history) ->
14 | eventStream = new Rx.Subject()
15 | history or= []
16 |
17 | state or= TodoListState()
18 |
19 | props = getViewState(state)
20 | props.eventStream = eventStream
21 | view = React.render TodoComponent(props), mountNode
22 |
23 | stateStream = dispatchActions(eventStream, state).share()
24 |
25 | subscribtion = stateStream.subscribe(
26 | (state) ->
27 | history.push(state)
28 | view.setProps getViewState(state)
29 | (err) -> throw new Error(err.stack))
30 |
31 |
32 | history_back = eventStream.filter(({action}) -> action is "history_back")
33 | .subscribe(
34 | ->
35 | subscribtion.dispose()
36 | history_back.dispose()
37 | history.pop()
38 | initApp(mountNode, history[history.length - 1], history))
39 |
40 |
41 | module.exports = initApp
42 |
43 |
--------------------------------------------------------------------------------
/todo/todo/dispatcher.coffee:
--------------------------------------------------------------------------------
1 | Rx = require 'rx'
2 | {setTodoText, addItem, destroyItem,
3 | checkItem, setCheckAll} = require './todo_state'
4 |
5 |
6 | dispatch_actions = (subject, initialState) ->
7 | mainInputKeyDown = subject
8 | .filter(({action}) -> action is "mainInputKeyDown")
9 |
10 | main_input_enter_key_down = mainInputKeyDown
11 | .filter(({keyCode}) -> keyCode is 13)
12 |
13 | todoTextChange = subject
14 | .filter(({action}) -> action is "mainInputTextChange")
15 | .map(({text}) -> setTodoText(text))
16 |
17 | addItemStream = main_input_enter_key_down
18 | .filter(({text}) -> text.length > 0)
19 | .map(({text}) -> addItem(text))
20 |
21 | destroyItemStream = subject
22 | .filter(({action}) -> action is "itemDestroy")
23 | .map(({id}) -> destroyItem(id))
24 |
25 | checkItemStream = subject
26 | .filter(({action}) -> action is "itemCheck")
27 | .map(({checked, id}) -> checkItem(id, checked))
28 |
29 | checkAll = subject
30 | .filter(({action}) -> action is "toggleCheckAll")
31 | .map(({checked}) -> setCheckAll(checked))
32 |
33 | Rx.Observable.merge(
34 | addItemStream, todoTextChange, destroyItemStream, checkItemStream, checkAll)
35 | .scan(initialState, (currentState, action) -> action(currentState))
36 |
37 |
38 | module.exports = dispatch_actions
--------------------------------------------------------------------------------
/todo/todo/index.coffee:
--------------------------------------------------------------------------------
1 | initApp = require './app'
2 | initApp(document.getElementById("todoapp"))
--------------------------------------------------------------------------------
/todo/todo/todo_state.coffee:
--------------------------------------------------------------------------------
1 | Immutable = require 'immutable'
2 | Record = Immutable.Record
3 | List = Immutable.List
4 |
5 |
6 | TodoListState = Record
7 | todoItems: List()
8 | todoText: ""
9 | doneItems: 0
10 | checkAll: false
11 |
12 |
13 | TodoItemState = Record
14 | text: ""
15 | id: ""
16 | complete: false
17 |
18 |
19 | # String -> TodoState -> TodoState
20 | setTodoText = (text) -> (state) ->
21 | state.set("todoText", text)
22 |
23 |
24 | # String -> TodoState -> TodoState
25 | addItem = (text) -> (state) ->
26 | currentItems = state.get("todoItems")
27 |
28 | newItem = TodoItemState
29 | text: text
30 | id: text
31 | complete: false
32 |
33 | state.set("todoItems", currentItems.unshift(newItem))
34 | .set("todoText", "")
35 |
36 |
37 | # String -> TodoState -> TodoState
38 | destroyItem = (id) -> (state) ->
39 | currentItems = state.get("todoItems")
40 | state.set("todoItems", currentItems.filter((i) -> i.id != id))
41 |
42 |
43 | # (String, String) -> TodoState -> TodoState
44 | checkItem = (id, checked) -> (state) ->
45 | currentItems = state.get("todoItems")
46 | state.set("todoItems", currentItems.map(
47 | (i) ->
48 | if i.id == id
49 | i.set("complete", checked)
50 | else
51 | i
52 | ))
53 |
54 | # Boolean -> TodoState -> TodoState
55 | setCheckAll = (checkAll) -> (state) ->
56 | currentItems = state.get("todoItems")
57 | state.set("todoItems",
58 | currentItems.map((i) -> i.set("complete", checkAll)))
59 |
60 |
61 | module.exports = {
62 | TodoListState
63 | setTodoText
64 | addItem
65 | destroyItem
66 | checkItem
67 | setCheckAll
68 | }
69 |
--------------------------------------------------------------------------------
/todo/todo/views/todo_item.coffee:
--------------------------------------------------------------------------------
1 | React = require 'react'
2 | PureRenderMixin = require('react/addons').addons.PureRenderMixin
3 | {div, h1, input, header, section, label, ul, li, button, p} = React.DOM
4 |
5 |
6 | TodoItem = React.createClass
7 | mixins: [PureRenderMixin]
8 |
9 | destroy: ->
10 | @props.eventStream.onNext
11 | action: "itemDestroy"
12 | id: @props.item.id
13 |
14 | check: (ev) ->
15 | @props.eventStream.onNext
16 | action: "itemCheck"
17 | checked: ev.target.checked
18 | id: @props.item.id
19 |
20 | edit: ->
21 | @props.eventStream.onNext
22 | action: "editItem"
23 | id: @props.item.id
24 | isEditMode: true
25 |
26 | render: ->
27 | li
28 | key: @props.item.id
29 | className: (
30 | @props.item.complete and "completed" or
31 | @props.item.isEditMode and "editing")
32 |
33 | div className:"view",
34 |
35 | input
36 | className: "toggle"
37 | type: "checkbox"
38 | checked: @props.item.complete
39 | onChange: @check
40 |
41 | label
42 | onDoubleClick: @edit
43 | @props.item.text
44 |
45 | button
46 | className: "destroy"
47 | onClick: @destroy
48 |
49 | if @props.item.isEditMode
50 | input
51 | className: "edit"
52 | value: @props.item.text
53 |
54 | module.exports = TodoItem
--------------------------------------------------------------------------------
/todo/todo/views/todo_list.coffee:
--------------------------------------------------------------------------------
1 | React = require 'react'
2 | PureRenderMixin = require('react/addons').addons.PureRenderMixin
3 | {createFactory} = React
4 | {div, h1, h2, input, header, section, label, ul, li, button, p, a} = React.DOM
5 | TodoItem = createFactory(require './todo_item')
6 | Immutable = require 'immutable'
7 | List = Immutable.List
8 |
9 | TodoList = React.createClass
10 | mixins: [PureRenderMixin]
11 |
12 | getDefaultProps: ->
13 | todoItems: List()
14 |
15 |
16 | mainInputKeyDown: (ev) ->
17 | @props.eventStream.onNext
18 | action: "mainInputKeyDown"
19 | keyCode: ev.keyCode
20 | text: ev.target.value
21 |
22 |
23 | mainInputTextChange: (ev) ->
24 | @props.eventStream.onNext
25 | action: "mainInputTextChange"
26 | text: ev.target.value
27 |
28 |
29 | toggleCheckAll: (ev) ->
30 | @props.eventStream.onNext
31 | action: "toggleCheckAll"
32 | checked: ev.target.checked
33 |
34 |
35 | render: ->
36 | div null,
37 | header {id: "header"},
38 |
39 | h1 null, "todos"
40 |
41 | input
42 | id: "new-todo"
43 | autoFocus: true
44 | value: @props.todoText
45 | placeholder: "What needs to be done?"
46 | onKeyDown: @mainInputKeyDown
47 | onChange: @mainInputTextChange
48 |
49 | section {id: "main"},
50 |
51 | input
52 | id: "toggle-all"
53 | type: "checkbox"
54 | checked: @props.checkAll
55 | onChange: @toggleCheckAll
56 |
57 | label
58 | htmlFor: "toggle-all"
59 | "Mark all as complete"
60 |
61 | ul {id: "todo-list"},
62 | @props.todoItems.toArray().map (item) =>
63 | TodoItem {
64 | key: item.id
65 | item
66 | eventStream: @props.eventStream
67 | }
68 |
69 | button
70 | onClick: => @props.eventStream.onNext {action: "history_back"}
71 | "Back"
72 |
73 |
74 | module.exports = TodoList
--------------------------------------------------------------------------------